一键导入
dashboard
Create FilamentPHP v4 dashboard pages with single-tab or multi-tab layouts, message callouts, and widget integration
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Create FilamentPHP v4 dashboard pages with single-tab or multi-tab layouts, message callouts, and widget integration
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | dashboard |
| description | Create FilamentPHP v4 dashboard pages with single-tab or multi-tab layouts, message callouts, and widget integration |
This skill generates FilamentPHP v4 dashboard pages that follow a consistent pattern:
Filament\Pages\Page$activeTab stateCRITICAL: Before generating dashboard pages, read:
/home/mwguerra/projects/mwguerra/claude-code-plugins/filament-specialist/skills/docs/references/general/06-navigation//home/mwguerra/projects/mwguerra/claude-code-plugins/filament-specialist/skills/docs/references/widgets/A dashboard page in this style has 3 pieces:
Filament Page class (PHP)
Filament\Pages\Page$view$activeTabgetTabs(): array and getActiveTabData(): ?arrayBlade view (resources/views/filament/{panel}/pages/{slug}.blade.php)
<x-filament-widgets::widgets :widgets="$activeTabData['widgets']" />Widgets (Filament Widgets)
::class stringsEach tab must follow this array schema:
[
'key' => 'overview', // Required: unique identifier
'title' => 'Overview', // Required: display title
'icon' => 'heroicon-o-chart-bar', // Optional: Heroicon name
'message' => '<strong>Note:</strong> ...', // Optional: HTML message
'messageColor' => 'blue', // Optional: blue|green|purple|orange|indigo|gray
'widgets' => [ // Optional: widget class references
\App\Filament\Admin\Widgets\SomeWidget::class,
\App\Filament\Admin\Widgets\AnotherWidget::class,
],
],
<?php
declare(strict_types=1);
namespace App\Filament\__PANEL__\Pages;
use BackedEnum;
use Filament\Pages\Page;
class __PAGE_CLASS__ extends Page
{
protected static string $view = 'filament.__PANEL_LOWER__.pages.__VIEW_SLUG__';
protected static string|BackedEnum|null $navigationIcon = '__HEROICON__';
protected static ?string $navigationLabel = '__NAV_LABEL__';
protected static \UnitEnum|string|null $navigationGroup = '__NAV_GROUP__';
protected static ?int $navigationSort = __NAV_SORT__;
public string $activeTab = '__DEFAULT_TAB_KEY__';
/**
* Get the tabs configuration for this dashboard page.
*
* @return array<int, array{
* key: string,
* title: string,
* icon?: string,
* message?: string,
* messageColor?: string,
* widgets?: array<int, class-string>
* }>
*/
public function getTabs(): array
{
return [
[
'key' => '__TAB_KEY__',
'icon' => '__TAB_ICON__',
'title' => '__TAB_TITLE__',
'message' => '__TAB_MESSAGE_HTML__',
'messageColor' => '__TAB_COLOR__',
'widgets' => [
// \App\Filament\__PANEL__\Widgets\ExampleWidget::class,
],
],
// Additional tabs...
];
}
/**
* Get the data for the currently active tab.
*/
public function getActiveTabData(): ?array
{
return collect($this->getTabs())->firstWhere('key', $this->activeTab);
}
}
<x-filament-panels::page>
@php
$tabs = $this->getTabs();
$activeTabData = $this->getActiveTabData();
// If activeTab is invalid, fall back to first tab to avoid empty page.
if (! $activeTabData && count($tabs) > 0) {
$this->activeTab = $tabs[0]['key'];
$activeTabData = $tabs[0];
}
@endphp
<div class="space-y-6">
{{-- Tabs Navigation --}}
<div class="border-b border-gray-200 dark:border-gray-700">
<nav class="-mb-px flex flex-wrap gap-x-8" aria-label="Tabs">
@foreach($tabs as $tab)
<button
type="button"
wire:click="$set('activeTab', '{{ $tab['key'] }}')"
@class([
'flex items-center gap-2 whitespace-nowrap border-b-2 py-4 px-1 text-sm font-medium',
'border-primary-500 text-primary-600 dark:border-primary-400 dark:text-primary-400' => $activeTab === $tab['key'],
'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:border-gray-600 dark:hover:text-gray-300' => $activeTab !== $tab['key'],
])
>
@if(!empty($tab['icon']))
<x-filament::icon :icon="$tab['icon']" class="h-5 w-5" />
@endif
{{ $tab['title'] }}
</button>
@endforeach
</nav>
</div>
{{-- Tab Content --}}
@if($activeTabData)
<div class="space-y-6">
@if(!empty($activeTabData['message']))
@php
$color = $activeTabData['messageColor'] ?? 'gray';
@endphp
<div @class([
'rounded-lg p-4 border',
'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800' => $color === 'blue',
'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800' => $color === 'green',
'bg-purple-50 dark:bg-purple-900/20 border-purple-200 dark:border-purple-800' => $color === 'purple',
'bg-orange-50 dark:bg-orange-900/20 border-orange-200 dark:border-orange-800' => $color === 'orange',
'bg-indigo-50 dark:bg-indigo-900/20 border-indigo-200 dark:border-indigo-800' => $color === 'indigo',
'bg-gray-50 dark:bg-gray-900/20 border-gray-200 dark:border-gray-800' => $color === 'gray',
])>
<p @class([
'text-sm',
'text-blue-700 dark:text-blue-300' => $color === 'blue',
'text-green-700 dark:text-green-300' => $color === 'green',
'text-purple-700 dark:text-purple-300' => $color === 'purple',
'text-orange-700 dark:text-orange-300' => $color === 'orange',
'text-indigo-700 dark:text-indigo-300' => $color === 'indigo',
'text-gray-700 dark:text-gray-300' => $color === 'gray',
])>
{!! $activeTabData['message'] !!}
</p>
</div>
@endif
@if(!empty($activeTabData['widgets']))
<x-filament-widgets::widgets :widgets="$activeTabData['widgets']" />
@endif
</div>
@endif
</div>
</x-filament-panels::page>
Use this when you want a page that behaves like "one tab" without showing navigation.
<?php
declare(strict_types=1);
namespace App\Filament\__PANEL__\Pages;
use BackedEnum;
use Filament\Pages\Page;
class __PAGE_CLASS__ extends Page
{
protected static string $view = 'filament.__PANEL_LOWER__.pages.__VIEW_SLUG__';
protected static string|BackedEnum|null $navigationIcon = '__HEROICON__';
protected static ?string $navigationLabel = '__NAV_LABEL__';
protected static \UnitEnum|string|null $navigationGroup = '__NAV_GROUP__';
protected static ?int $navigationSort = __NAV_SORT__;
public string $activeTab = 'main';
/**
* Get the tabs configuration (single tab for this page).
*
* @return array<int, array{
* key: string,
* title: string,
* message?: string,
* messageColor?: string,
* widgets?: array<int, class-string>
* }>
*/
public function getTabs(): array
{
return [
[
'key' => 'main',
'title' => '__PAGE_TITLE__',
'message' => '__MESSAGE_HTML__',
'messageColor' => '__COLOR__',
'widgets' => [
// \App\Filament\__PANEL__\Widgets\ExampleWidget::class,
],
],
];
}
/**
* Get the data for the active tab (always the single main tab).
*/
public function getActiveTabData(): ?array
{
return $this->getTabs()[0] ?? null;
}
}
<x-filament-panels::page>
@php
$activeTabData = $this->getActiveTabData();
@endphp
<div class="space-y-6">
@if($activeTabData)
@if(!empty($activeTabData['message']))
@php $color = $activeTabData['messageColor'] ?? 'gray'; @endphp
<div @class([
'rounded-lg p-4 border',
'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800' => $color === 'blue',
'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800' => $color === 'green',
'bg-purple-50 dark:bg-purple-900/20 border-purple-200 dark:border-purple-800' => $color === 'purple',
'bg-orange-50 dark:bg-orange-900/20 border-orange-200 dark:border-orange-800' => $color === 'orange',
'bg-indigo-50 dark:bg-indigo-900/20 border-indigo-200 dark:border-indigo-800' => $color === 'indigo',
'bg-gray-50 dark:bg-gray-900/20 border-gray-200 dark:border-gray-800' => $color === 'gray',
])>
<p @class([
'text-sm',
'text-blue-700 dark:text-blue-300' => $color === 'blue',
'text-green-700 dark:text-green-300' => $color === 'green',
'text-purple-700 dark:text-purple-300' => $color === 'purple',
'text-orange-700 dark:text-orange-300' => $color === 'orange',
'text-indigo-700 dark:text-indigo-300' => $color === 'indigo',
'text-gray-700 dark:text-gray-300' => $color === 'gray',
])>
{!! $activeTabData['message'] !!}
</p>
</div>
@endif
@if(!empty($activeTabData['widgets']))
<x-filament-widgets::widgets :widgets="$activeTabData['widgets']" />
@endif
@endif
</div>
</x-filament-panels::page>
When creating a dashboard page, collect or assume defaults for:
| Input | Description | Example |
|---|---|---|
| Page class name | PascalCase class name | BillingDashboard |
| Panel | Panel name (Admin, Support, etc.) | Admin |
| View slug | Kebab-case slug for blade file | billing-dashboard |
| Navigation label | Display text in sidebar | Billing |
| Navigation group | Group in sidebar | Analytics |
| Navigation icon | Heroicon name | heroicon-o-chart-bar |
| Navigation sort | Numeric sort order | 10 |
| Mode | single or multi tab | multi |
| Tabs | Array of tab definitions | See schema above |
| Default tab key | First active tab | overview |
$view property$view matches the Blade pathactiveTab key exists in getTabs()key and titleactiveTab invalid{!! !!} only with trusted HTML<?php
declare(strict_types=1);
namespace App\Filament\Admin\Pages;
use BackedEnum;
use Filament\Pages\Page;
class Analytics extends Page
{
protected static string $view = 'filament.admin.pages.analytics';
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-chart-bar';
protected static ?string $navigationLabel = 'Analytics';
protected static \UnitEnum|string|null $navigationGroup = 'Reports';
protected static ?int $navigationSort = 10;
public string $activeTab = 'overview';
/**
* @return array<int, array{
* key: string,
* title: string,
* icon?: string,
* message?: string,
* messageColor?: string,
* widgets?: array<int, class-string>
* }>
*/
public function getTabs(): array
{
return [
[
'key' => 'overview',
'icon' => 'heroicon-o-home',
'title' => 'Overview',
'message' => '<strong>Overview:</strong> Key metrics and performance indicators at a glance.',
'messageColor' => 'blue',
'widgets' => [
\App\Filament\Admin\Widgets\StatsOverview::class,
\App\Filament\Admin\Widgets\RevenueChart::class,
],
],
[
'key' => 'users',
'icon' => 'heroicon-o-users',
'title' => 'Users',
'message' => '<strong>User Analytics:</strong> Track user growth, engagement, and retention metrics.',
'messageColor' => 'green',
'widgets' => [
\App\Filament\Admin\Widgets\UserGrowthChart::class,
\App\Filament\Admin\Widgets\ActiveUsersWidget::class,
],
],
[
'key' => 'revenue',
'icon' => 'heroicon-o-currency-dollar',
'title' => 'Revenue',
'message' => '<strong>Revenue Analytics:</strong> Monitor income streams and financial performance.',
'messageColor' => 'purple',
'widgets' => [
\App\Filament\Admin\Widgets\RevenueBreakdown::class,
\App\Filament\Admin\Widgets\TopProducts::class,
],
],
];
}
public function getActiveTabData(): ?array
{
return collect($this->getTabs())->firstWhere('key', $this->activeTab);
}
}
$activeTab must match a key from getTabs()message is rendered with {!! !!} — only use trusted HTML::class stringsheroicon-o-chart-bar)blue, green, purple, orange, indigo, grayGenerated dashboard pages include:
Analyze, test, and prepare apps for production with Pest and Playwright testing
This skill should be used when encountering errors during development, when the user mentions an error, when debugging issues, or when asked to "fix an error", "debug this", "why is this failing", "solve this error". Provides intelligent error recognition, solution lookup from past errors, and error logging for future reference.
This skill should be used when the user asks to "manage notes", "update vault", "add to obsidian", "document in vault", "search my notes", "find related notes", or when working with Obsidian vault files. Also triggers when discussing knowledge management, note organization, or when Claude Code auto-captures commits, tasks, or component creation.
This skill should be used when conducting PRD interviews, creating product requirements documents, planning new features, documenting bug fixes, or when using commands like "/prd-builder:prd", "/prd-builder:feature", "/prd-builder:bugfix", or "/prd-builder:refine". Provides comprehensive interview frameworks and question templates for building thorough PRDs.
Execute complete user flow testing with Playwright MCP, testing end-to-end journeys through the application
Systematically test all pages for errors, functionality, and proper rendering using Playwright MCP