harvirsidhu / filament-cards
A Filament-native cards plugin for organizing pages and resources into a card-based navigation hub.
Fund package maintenance!
Requires
- php: ^8.2
- filament/filament: ^4.0|^5.0
- spatie/laravel-package-tools: ^1.15.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- nunomaduro/collision: ^8.0
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.7|^4.0
- pestphp/pest-plugin-arch: ^3.0|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
- pestphp/pest-plugin-livewire: ^3.0|^4.0
- rector/rector: ^2.0
- spatie/laravel-ray: ^1.26
README
A Filament-native plugin that turns your pages and resources into a card-based navigation hub. Built to feel like it belongs in Filament's core -- uses the same API patterns (label, schema, columnSpan, visible/hidden) and integrates natively with Clusters and Resources.
Best used as a Cluster or Resource front page that auto-discovers child pages and resources, respecting Filament's existing navigation configuration. Also works standalone as a general-purpose settings hub.
Screenshot
Documentation
Index
- Requirements
- Installation
- Quick Start (Manual Cards)
- Use Case: Cluster Front Page (Primary)
- Use Case: Resource Hub
- Use Case: Standalone Page
- API Reference: CardItem
- API Reference: CardGroup
- API Reference: CardsPage Configuration
- Advanced: Dynamic Registration
- Full Example (Putting It All Together)
- Optional: Filament Plugin Registration
- License
Feature Overview
- Auto-discovery for Cluster and Resource pages with built-in authorization checks.
- Filament-native API (
label,description,schema,visible,hidden, etc.). - Grouping and layout controls via
CardGroup, columns, spans, compact mode, and collapse behavior. - Flexible visibility using component-level methods/properties or page-level include/exclude hooks.
- Manual + discovered cards together so you can combine internal routes and external links.
- Dynamic registration for modular apps and package-driven extension points.
Requirements
- PHP 8.2+
- Laravel 11+
- Filament v4 or v5
Installation
Install via Composer:
composer require harvirsidhu/filament-cards
Theme Setup
Since the plugin uses Tailwind CSS classes, add the plugin's views to your theme.
For Filament v4.x / v5.x, add this line to your theme.css:
@source '../../../../vendor/harvirsidhu/filament-cards/resources/views';
Then rebuild your assets:
npm run build
Quick Start (Manual Cards)
The simplest possible cards page in 10 lines:
namespace App\Filament\Pages; use Harvirsidhu\FilamentCards\Filament\Pages\CardsPage; use Harvirsidhu\FilamentCards\CardItem; class ControlPanel extends CardsPage { protected static ?string $navigationIcon = 'heroicon-o-squares-2x2'; protected static function getCards(): array { return [ CardItem::make(CompanySettings::class), CardItem::make(BillingSettings::class), ]; } }
Use Case: Cluster Front Page (Primary)
The most powerful way to use this plugin is as the front page of a Cluster. Auto-discovery reads all pages and resources in the cluster and creates cards automatically.
Step 1: Define the Cluster
namespace App\Filament\Clusters; use Filament\Clusters\Cluster; class Settings extends Cluster { protected static ?string $navigationIcon = 'heroicon-o-cog-8-tooth'; }
Step 2: Create the CardsPage
namespace App\Filament\Clusters\Settings\Pages; use App\Filament\Clusters\Settings; use Harvirsidhu\FilamentCards\Filament\Pages\CardsPage; class SettingsHub extends CardsPage { protected static ?string $cluster = Settings::class; protected static ?int $navigationSort = -1; protected static function getCards(): array { return static::discoverClusterCards(); } }
Step 3: Your Cluster Pages Work as Normal
No extra traits or changes needed on any of your pages.
To add a description to the card, simply add a $navigationDescription property to your page class:
namespace App\Filament\Clusters\Settings\Pages; use Filament\Pages\Page; use App\Filament\Clusters\Settings; class CompanySettings extends Page { protected static ?string $cluster = Settings::class; protected static ?string $navigationIcon = 'heroicon-o-building-office'; // Optional: Add a description to the card public static ?string $navigationDescription = 'Manage company name, address, and branding.'; }
Breadcrumbs work automatically: Dashboard > Settings > Company Settings. Filament's cluster breadcrumb system handles everything -- no custom traits needed.
discoverClusterCards()
- Reads all pages and resources registered to the Cluster
- Excludes the CardsPage itself
- Checks
canAccess()on each component (respects authorization) - Checks cards visibility via
showInFilamentCards()or$showInFilamentCards(if defined on the component), otherwise falls back toshouldRegisterNavigation() - Uses each page's
$navigationLabel,$navigationIcon, and URL - Reads
getNavigationBadge()/getNavigationBadgeColor()when available - New: Checks for
$navigationDescriptionproperty (orgetNavigationDescription()method) on the page class - Groups cards by
getFilamentCardsGroup()/$filamentCardsGroup(if defined), otherwise falls back to$navigationGroup - Sorts by
$navigationSort
Hiding Auto-Discovered Cards
You can hide a Page/Resource from auto-discovery in two ways.
1) On the Page/Resource itself (recommended)
class InternalToolsPage extends Page { public static function showInFilamentCards(): bool { return false; } }
Or with a static property:
class AuditLogsResource extends Resource { public static bool $showInFilamentCards = false; }
To force-show a component in cards even if shouldRegisterNavigation() is false:
class HiddenFromSidebarPage extends Page { public static function shouldRegisterNavigation(): bool { return false; } public static function showInFilamentCards(): bool { return true; } }
2) On the CardsPage (central control)
class SettingsHub extends CardsPage { protected static array $excludedClusterComponents = [ AuditLogsResource::class, InternalToolsPage::class, ]; }
Grouping Auto-Discovered Cards
By default, discovered cards use each component's navigation group (getNavigationGroup() / $navigationGroup).
If you want a different group just for cards, define it on the Page/Resource:
class CompanySettings extends Page { public static function getFilamentCardsGroup(): ?string { return 'Business Settings'; } }
Or with a static property:
class BillingResource extends Resource { public static ?string $filamentCardsGroup = 'Finance'; }
Mixing Auto-Discovered and Manual Cards
protected static function getCards(): array { return [ ...static::discoverClusterCards(), CardGroup::make('External Links') ->schema([ CardItem::make('https://docs.example.com') ->label('Documentation') ->icon('heroicon-o-book-open') ->openUrlInNewTab(), ]), ]; }
Use Case: Resource Hub
When a Resource has many custom pages, use discoverResourceCards() to auto-create cards for each:
namespace App\Filament\Resources\UserResource\Pages; use App\Filament\Resources\UserResource; use Harvirsidhu\FilamentCards\Filament\Pages\CardsPage; class UserSettingsHub extends CardsPage { protected static string $resource = UserResource::class; protected static function getCards(): array { return static::discoverResourceCards(); } }
Use Case: Standalone Page
Without a Cluster or Resource, define cards manually:
use Harvirsidhu\FilamentCards\CardItem; use Harvirsidhu\FilamentCards\CardGroup; use Harvirsidhu\FilamentCards\Filament\Pages\CardsPage; class ControlPanel extends CardsPage { protected static ?string $navigationIcon = 'heroicon-o-squares-2x2'; protected static function getCards(): array { return [ CardGroup::make('General') ->icon('heroicon-o-cog') ->description('Core settings') ->schema([ CardItem::make(CompanySettings::class)->color('primary'), CardItem::make(BillingSettings::class)->color('success'), ]), CardItem::make('/external/docs') ->label('Documentation') ->icon('heroicon-o-document-text') ->openUrlInNewTab(), ]; } }
API Reference: CardItem
Creating a Card Item
Pass a Filament Page class, Resource class, or a URL string:
CardItem::make(CompanySettings::class) // Filament Page CardItem::make(UserResource::class) // Filament Resource CardItem::make('/custom/path') // URL string CardItem::make('https://example.com') // External URL
When a Page or Resource class is passed, the card automatically resolves its label, icon, badge, badgeColor, and url from the class's navigation properties.
label()
Override the card title. Accepts a string or Closure:
CardItem::make(CompanySettings::class) ->label('Company') CardItem::make(CompanySettings::class) ->label(fn () => __('settings.company'))
description()
Add a subtitle below the card title:
CardItem::make(CompanySettings::class) ->description('Manage company name, address, and branding')
Auto-Discovery: If you are using discoverClusterCards() or discoverResourceCards(), you can add a static property to your page class instead:
class CompanySettings extends Page { public static ?string $navigationDescription = 'Manage company details.'; }
badge()
Add a badge to the right side of the card title:
CardItem::make(CompanySettings::class) ->badge('Beta')
badgeColor()
Set the badge color using Filament color names (or an equivalent color definition):
CardItem::make(CompanySettings::class) ->badge('12') ->badgeColor('primary')
Auto-Discovery: If you are using discoverClusterCards() or discoverResourceCards(), badge values are read from getNavigationBadge() and getNavigationBadgeColor() when available on the discovered Page/Resource.
icon()
Override the card icon:
CardItem::make('/path') ->icon('heroicon-o-building-office')
url() and openUrlInNewTab()
Override the URL or open in a new tab:
CardItem::make(CompanySettings::class) ->url('https://custom-url.com') ->openUrlInNewTab()
alignment()
Control the text alignment of the card content. Options: Start, Center, End, Justify:
use Filament\Support\Enums\Alignment; CardItem::make(CompanySettings::class) ->alignment(Alignment::Center)
visible() and hidden()
Control card visibility. Accepts a boolean or Closure:
CardItem::make(BillingSettings::class) ->visible(fn () => auth()->user()->can('manage-billing')) CardItem::make(DangerZone::class) ->hidden(fn () => ! auth()->user()->isAdmin())
color()
Add a color accent to the card. Supports Filament's color system:
CardItem::make(CompanySettings::class)->color('primary') CardItem::make(BillingSettings::class)->color('success') CardItem::make(DangerZone::class)->color('danger') CardItem::make(Notifications::class)->color('warning') CardItem::make(ApiSettings::class)->color('info') CardItem::make(LegacySettings::class)->color('gray')
Available colors: primary, success, danger, warning, info, gray.
disabled()
Show the card but make it non-clickable with reduced opacity:
CardItem::make(DangerZone::class) ->disabled(fn () => ! auth()->user()->isAdmin())
sort()
Control the order of cards within a group:
CardItem::make(CompanySettings::class)->sort(1) CardItem::make(BillingSettings::class)->sort(2) CardItem::make(NotificationPrefs::class)->sort(3)
columnSpan() and columnSpanFull()
Control how many grid columns a card occupies:
// Span 2 columns CardItem::make(CompanySettings::class)->columnSpan(2) // Span the full width CardItem::make(NotificationPrefs::class)->columnSpanFull() // Responsive spans CardItem::make(CompanySettings::class)->columnSpan([ 'default' => 1, 'md' => 2, 'lg' => 3, ])
extraAttributes()
Add custom HTML attributes to the card element:
CardItem::make(CompanySettings::class) ->extraAttributes([ 'data-analytics' => 'company-settings', 'id' => 'company-card', ])
API Reference: CardGroup
Groups organize cards under a collapsible header, similar to Filament's Section.
Creating a Group
use Harvirsidhu\FilamentCards\CardGroup; CardGroup::make('General Settings') ->schema([ CardItem::make(CompanySettings::class), CardItem::make(BillingSettings::class), ])
schema()
Define the card items in the group:
CardGroup::make('General') ->schema([ CardItem::make(CompanySettings::class), CardItem::make(BillingSettings::class), ])
columns()
Override the grid columns for this specific group. You can use a single integer, or a responsive array (same style as Filament widgets):
CardGroup::make('Wide Cards') ->columns(2) ->schema([...]) CardGroup::make('Wide Cards') ->columns([ 'md' => 2, 'xl' => 4, ]) ->schema([...])
collapsible() and collapsed()
Make the group collapsible, optionally starting collapsed:
CardGroup::make('Advanced') ->collapsible() ->schema([...]) CardGroup::make('Advanced') ->collapsed() // Starts collapsed, implicitly collapsible ->schema([...]) CardGroup::make('Advanced') ->collapsed(fn () => ! auth()->user()->isAdmin()) ->schema([...])
compact()
Reduce padding and gaps for a denser layout:
CardGroup::make('Quick Links') ->compact() ->schema([...])
label(), description(), icon()
Customize the group header:
CardGroup::make('General') ->label('General Settings') ->description('Core application configuration') ->icon('heroicon-o-cog') ->schema([...])
Group-level visible() and hidden()
Hide an entire group conditionally:
CardGroup::make('Admin Only') ->hidden(fn () => ! auth()->user()->isAdmin()) ->schema([...])
API Reference: CardsPage Configuration
Customize the CardsPage with static properties:
$columns
Default number of grid columns (default: 3).
Supports Filament widget-style responsive values:
class ControlPanel extends CardsPage { protected static string|int|array $columns = 4; } class ControlPanel extends CardsPage { protected static string|int|array $columns = [ 'md' => 2, 'xl' => 4, ]; }
$itemsAlignment
Alignment of card content. Options: Start, Center, End, Justify (default: Center):
use Filament\Support\Enums\Alignment; class ControlPanel extends CardsPage { protected static Alignment $itemsAlignment = Alignment::Center; }
$iconSize
Size of card icons. Options: Small, Medium, Large (default: Medium):
use Filament\Support\Enums\IconSize; class ControlPanel extends CardsPage { protected static IconSize $iconSize = IconSize::Small; }
$iconInlined
Display the icon inline with the title instead of stacked above it:
class ControlPanel extends CardsPage { protected static bool $iconInlined = true; }
$iconPosition
Controls whether the icon appears before or after the label. Options: Before, After (default: Before):
use Filament\Support\Enums\IconPosition; class ControlPanel extends CardsPage { protected static IconPosition $iconPosition = IconPosition::After; }
$excludedClusterComponents
Exclude specific Cluster pages/resources from discoverClusterCards():
class SettingsHub extends CardsPage { protected static array $excludedClusterComponents = [ AuditLogsResource::class, InternalToolsPage::class, ]; }
$excludedResourcePages
Exclude specific Resource pages from discoverResourceCards():
class UserSettingsHub extends CardsPage { protected static array $excludedResourcePages = [ UserResource\Pages\DangerZone::class, ]; }
shouldIncludeDiscoveredCard()
Override this hook for advanced, centralized filtering:
protected static function shouldIncludeDiscoveredCard(string $component): bool { if (! parent::shouldIncludeDiscoveredCard($component)) { return false; } return $component !== BetaFeaturePage::class; }
Advanced: Dynamic Registration
Add cards to a CardsPage from outside the class -- useful for modular applications or packages:
use App\Filament\Pages\ControlPanel; use Harvirsidhu\FilamentCards\CardItem; // In a service provider boot() method: ControlPanel::addCards([ CardItem::make(UserManagement::class) ->label('User Accounts') ->icon('heroicon-o-users') ->description('Manage roles, permissions, and user accounts'), ]);
Full Example (Putting It All Together)
use Harvirsidhu\FilamentCards\CardItem; use Harvirsidhu\FilamentCards\CardGroup; use Harvirsidhu\FilamentCards\Filament\Pages\CardsPage; use Filament\Support\Enums\Alignment; use Filament\Support\Enums\IconPosition; use Filament\Support\Enums\IconSize; class SettingsHub extends CardsPage { protected static ?string $navigationIcon = 'heroicon-o-cog-8-tooth'; protected static int $columns = 3; protected static Alignment $itemsAlignment = Alignment::Center; protected static IconSize $iconSize = IconSize::Medium; protected static IconPosition $iconPosition = IconPosition::Before; protected static function getCards(): array { return [ CardGroup::make('General') ->icon('heroicon-o-cog') ->description('Core application settings') ->collapsible() ->schema([ CardItem::make(CompanySettings::class) ->color('primary'), CardItem::make(BillingSettings::class) ->visible(fn () => auth()->user()->can('manage-billing')) ->color('success') ->sort(2), CardItem::make(NotificationPrefs::class) ->description('Email, SMS & push notification preferences') ->columnSpanFull(), ]), CardGroup::make('Danger Zone') ->icon('heroicon-o-exclamation-triangle') ->collapsed() ->columns(2) ->schema([ CardItem::make(DangerZone::class) ->color('danger') ->disabled(fn () => ! auth()->user()->isAdmin()), ]), CardItem::make('https://docs.example.com') ->label('Documentation') ->icon('heroicon-o-book-open') ->openUrlInNewTab() ->extraAttributes(['data-track' => 'docs']), ]; } }
Optional: Filament Plugin Registration
Optionally register the plugin in your panel provider:
use Harvirsidhu\FilamentCards\FilamentCardsPlugin; public function panel(Panel $panel): Panel { return $panel ->plugins([ FilamentCardsPlugin::make(), ]); }
License
The MIT License (MIT). Please see License File for more information.
