harvirsidhu/filament-cards
| Install | |
|---|---|
composer require harvirsidhu/filament-cards |
|
| Latest Version: | v1.0.6 |
| PHP: | ^8.2 |
| License: | MIT |
| Last Updated: | Feb 19, 2026 |
| Links: | GitHub · Packagist |
Filament Cards
Turn any Filament page into a card-based navigation hub — perfect for Settings hubs, Cluster front pages, Resource dashboards, or any place you want a clean grid of links instead of a sidebar tree.
It feels like part of Filament: same API patterns (label, schema, columnSpan, visible/hidden), respects your existing navigation config, and auto-discovers Cluster/Resource pages with full authorization checks.

Table of Contents
- Why use this?
- Which approach fits your case?
- Requirements
- Installation
- 60-Second Quick Start
- Use Case A — Cluster Front Page (most common)
- Use Case B — Resource Hub
- Use Case C — Standalone Settings Page
- API Reference —
CardItem - API Reference —
CardGroup - API Reference —
CardsPageConfiguration - API Reference — Page/Resource Hooks (for auto-discovery)
- Advanced — Dynamic Registration
- Advanced — Custom Discovery Filtering
- Full Example
- Optional — Plugin Registration
- License
Why use this?
| Feature | What it gives you |
|---|---|
| Auto-discovery | Reads pages/resources in a Cluster (or Resource) and creates cards automatically — no manual list to maintain. |
| Filament-native API | label, description, schema, visible, hidden, columnSpan — same patterns you already use. |
| Authorization-aware | Calls canAccess() on each component before rendering. |
| Grouping & layout | CardGroup, columns, spans, compact mode, collapsible sections. |
| Flexible visibility | Per-card or per-page hooks; central exclude lists; closure-based conditions. |
| Manual + discovered | Mix auto-discovered cards with hand-crafted ones (e.g., external links). |
| Dynamic registration | Add cards from service providers — useful for modular apps and packages. |
| Client-side search | Optional live search that filters by label, description, badge, and custom keywords. |
Which approach fits your case?
| Your situation | Use this |
|---|---|
| You have a Cluster with several pages and want a landing page | Cluster Front Page |
| You have a Resource with many custom pages | Resource Hub |
| You just want a generic settings/control panel with manual cards | Standalone Page |
| You want to add cards from a package or module | Dynamic Registration |
Requirements
- PHP 8.2+
- Laravel 11+
- Filament v4 or v5
Installation
Install the package via Composer:
composer require harvirsidhu/filament-cards
Theme setup (required for styling)
The plugin uses Tailwind classes, so add its views to your Filament theme so Tailwind picks them up.
In your theme.css, add:
@source '../../../../vendor/harvirsidhu/filament-cards/resources/views';
Then rebuild assets:
npm run build
60-Second Quick Start
The smallest possible cards page:
namespace App\Filament\Pages;
use Harvirsidhu\FilamentCards\CardItem;
use Harvirsidhu\FilamentCards\Filament\Pages\CardsPage;
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),
];
}
}
That's it — Filament will pick up your new page, and clicking each card navigates to the linked page.
Use Case A — Cluster Front Page (most common)
The most powerful pattern: a CardsPage that automatically lists every page and resource in its Cluster.
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 front-page 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; // Show first in the cluster
protected static function getCards(): array
{
return static::discoverClusterCards();
}
}
Step 3 — Your other Cluster pages need no changes
Your existing pages just work. To add a description on the card, add a single property:
class CompanySettings extends Page
{
protected static ?string $cluster = Settings::class;
protected static ?string $navigationIcon = 'heroicon-o-building-office';
public static ?string $navigationDescription = 'Manage company name, address, and branding.';
}
Breadcrumbs (Dashboard > Settings > Company Settings) work automatically.
What discoverClusterCards() does
In order, for every component registered to the Cluster:
- Skips the CardsPage itself.
- Calls
canAccess()(respects authorization). - Checks
showInFilamentCards()/$showInFilamentCards— falls back toshouldRegisterNavigation(). - Reads
$navigationLabel,$navigationIcon, and the resolved URL. - Reads
getNavigationBadge()/getNavigationBadgeColor()if defined. - Reads
$navigationDescription(orgetNavigationDescription()) if defined. - Groups by
getFilamentCardsGroup()/$filamentCardsGroup— falls back to$navigationGroup. - Sorts each group by
$navigationSort.
Hiding a card from auto-discovery
Option 1 — on the Page/Resource (recommended):
class InternalToolsPage extends Page
{
public static function showInFilamentCards(): bool
{
return false;
}
}
// or as a property
class AuditLogsResource extends Resource
{
public static bool $showInFilamentCards = false;
}
To force-show a page that's hidden from the sidebar:
class HiddenFromSidebarPage extends Page
{
public static function shouldRegisterNavigation(): bool
{
return false;
}
public static function showInFilamentCards(): bool
{
return true;
}
}
Option 2 — central exclude list on the CardsPage:
class SettingsHub extends CardsPage
{
protected static array $excludedClusterComponents = [
AuditLogsResource::class,
InternalToolsPage::class,
];
}
Custom group name (different from the sidebar)
class CompanySettings extends Page
{
public static function getFilamentCardsGroup(): ?string
{
return 'Business Settings';
}
}
// or property form
class BillingResource extends Resource
{
public static ?string $filamentCardsGroup = 'Finance';
}
Mixing discovered cards with manual ones
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 B — Resource Hub
When a Resource has many custom pages, use discoverResourceCards() to auto-create a card 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();
}
}
Same hooks (showInFilamentCards, $navigationDescription, etc.) work here. To exclude specific pages:
protected static array $excludedResourcePages = [
UserResource\Pages\DangerZone::class,
];
Use Case C — Standalone Settings Page
No Cluster, no Resource — just a manually-curated cards page:
use Harvirsidhu\FilamentCards\CardGroup;
use Harvirsidhu\FilamentCards\CardItem;
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
CardItem represents a single clickable card. 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') // Internal URL
CardItem::make('https://example.com') // External URL
When given a Page/Resource class, the card auto-resolves label, icon, badge, badgeColor, and url from the class's navigation properties.
Method overview
| Method | Purpose |
|---|---|
label() |
Override the card title |
description() |
Subtitle below the title |
badge() |
Right-aligned badge |
badgeColor() |
Badge color |
icon() |
Override the card icon |
url() / openUrlInNewTab() |
Override target URL |
alignment() |
Text alignment inside the card |
color() |
Color accent on the card |
visible() / hidden() |
Conditional rendering |
disabled() |
Render but make non-clickable |
sort() |
Order within a group |
columnSpan() / columnSpanFull() |
Grid span |
searchKeywords() |
Extra terms for the search bar |
extraAttributes() |
Custom HTML attributes |
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 title:
CardItem::make(CompanySettings::class)
->description('Manage company name, address, and branding')
Auto-discovery shortcut: add
public static ?string $navigationDescriptionto the page/resource anddiscoverClusterCards()/discoverResourceCards()will pick it up automatically.
badge() / badgeColor()
CardItem::make(CompanySettings::class)
->badge('Beta')
->badgeColor('primary')
Auto-discovery: read from
getNavigationBadge()/getNavigationBadgeColor()when present on the discovered Page/Resource.
icon()
CardItem::make('/path')
->icon('heroicon-o-building-office')
url() / openUrlInNewTab()
CardItem::make(CompanySettings::class)
->url('https://custom-url.com')
->openUrlInNewTab()
alignment()
Text alignment inside the card. Options: Start, Center, End, Justify.
use Filament\Support\Enums\Alignment;
CardItem::make(CompanySettings::class)
->alignment(Alignment::Center)
color()
Color accent on the card. Available: primary, success, danger, warning, info, gray.
CardItem::make(BillingSettings::class)->color('success')
CardItem::make(DangerZone::class)->color('danger')
visible() / hidden()
Boolean or Closure:
CardItem::make(BillingSettings::class)
->visible(fn () => auth()->user()->can('manage-billing'))
CardItem::make(DangerZone::class)
->hidden(fn () => ! auth()->user()->isAdmin())
disabled()
Render the card but make it non-clickable with reduced opacity:
CardItem::make(DangerZone::class)
->disabled(fn () => ! auth()->user()->isAdmin())
sort()
Order cards within a group:
CardItem::make(CompanySettings::class)->sort(1)
CardItem::make(BillingSettings::class)->sort(2)
columnSpan() / columnSpanFull()
CardItem::make(CompanySettings::class)->columnSpan(2)
CardItem::make(NotificationPrefs::class)->columnSpanFull()
// Responsive
CardItem::make(CompanySettings::class)->columnSpan([
'default' => 1,
'md' => 2,
'lg' => 3,
])
searchKeywords()
Extra terms used by the page's search bar (when $searchable = true on the CardsPage). Useful for aliases and jargon — the keywords are not displayed.
CardItem::make(BillingSettings::class)
->searchKeywords(['invoices', 'payments', 'subscriptions', 'finance'])
CardItem::make(CompanySettings::class)
->searchKeywords('organisation') // single string ok
CardItem::make(LegacyTools::class)
->searchKeywords(fn () => $this->resolveLegacyAliases())
Auto-discovery: declare on the page/resource directly:
public static array $filamentCardsSearchKeywords = ['organisation', 'org']; // …or as a method public static function getFilamentCardsSearchKeywords(): array { return ['organisation', 'org']; }
extraAttributes()
Custom HTML attributes on 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 — like Filament's Section.
Method overview
| Method | Purpose |
|---|---|
schema() |
Cards inside the group |
label() / description() / icon() |
Header customization |
columns() |
Override grid columns for this group |
collapsible() / collapsed() |
Collapse behavior |
compact() |
Tighter padding/gaps |
visible() / hidden() |
Hide the whole group |
schema()
CardGroup::make('General')
->schema([
CardItem::make(CompanySettings::class),
CardItem::make(BillingSettings::class),
])
label() / description() / icon() (group)
CardGroup::make('General')
->label('General Settings')
->description('Core application configuration')
->icon('heroicon-o-cog')
->schema([...])
columns()
Integer or responsive array (Filament widget style):
CardGroup::make('Wide Cards')
->columns(2)
->schema([...])
CardGroup::make('Wide Cards')
->columns([
'md' => 2,
'xl' => 4,
])
->schema([...])
collapsible() / collapsed()
CardGroup::make('Advanced')->collapsible()->schema([...])
// Starts collapsed (implicitly collapsible)
CardGroup::make('Advanced')->collapsed()->schema([...])
CardGroup::make('Advanced')
->collapsed(fn () => ! auth()->user()->isAdmin())
->schema([...])
compact()
CardGroup::make('Quick Links')
->compact()
->schema([...])
Group-level visible() / hidden()
CardGroup::make('Admin Only')
->hidden(fn () => ! auth()->user()->isAdmin())
->schema([...])
API Reference — CardsPage Configuration
Configure the whole page with static properties.
| Property | Type | Default | Purpose |
|---|---|---|---|
$columns |
int|string|array |
3 |
Grid columns (responsive supported) |
$itemsAlignment |
Alignment |
Center |
Alignment of card content |
$iconSize |
IconSize |
Medium |
Card icon size |
$iconInlined |
bool |
false |
Inline icon with title (vs stacked) |
$iconPosition |
IconPosition |
Before |
Icon before or after the label |
$searchable |
bool |
false |
Show a client-side search bar |
$searchPlaceholder |
?string |
null ('Search…') |
Placeholder text for the search input |
$excludedClusterComponents |
array |
[] |
Skip these classes in discoverClusterCards() |
$excludedResourcePages |
array |
[] |
Skip these classes in discoverResourceCards() |
$columns
protected static string|int|array $columns = 4;
// Responsive
protected static string|int|array $columns = [
'md' => 2,
'xl' => 4,
];
$itemsAlignment
use Filament\Support\Enums\Alignment;
protected static Alignment $itemsAlignment = Alignment::Center;
$iconSize
use Filament\Support\Enums\IconSize;
protected static IconSize $iconSize = IconSize::Small;
$iconInlined
protected static bool $iconInlined = true;
$iconPosition
use Filament\Support\Enums\IconPosition;
protected static IconPosition $iconPosition = IconPosition::After;
$searchable
Renders a search bar at the top right. Filters cards live by label, description, badge, and any searchKeywords(). Empty groups are auto-hidden. Filtering happens client-side via Alpine.js — no server round-trips.
protected static bool $searchable = true;
$searchPlaceholder
protected static bool $searchable = true;
protected static ?string $searchPlaceholder = 'Find a tool...';
$excludedClusterComponents
protected static array $excludedClusterComponents = [
AuditLogsResource::class,
InternalToolsPage::class,
];
$excludedResourcePages
protected static array $excludedResourcePages = [
UserResource\Pages\DangerZone::class,
];
API Reference — Page/Resource Hooks (for auto-discovery)
These are read off your existing Pages and Resources by discoverClusterCards() / discoverResourceCards(). You add them only to pages you want discovered.
| Hook | Form | Purpose |
|---|---|---|
$navigationDescription |
public static ?string |
Subtitle text on the card |
getNavigationDescription() |
public static function (): ?string |
Same as above (method form) |
$showInFilamentCards |
public static bool |
Whether to include in cards |
showInFilamentCards() |
public static function (): bool |
Same (method form) |
$filamentCardsGroup |
public static ?string |
Override group name (otherwise $navigationGroup) |
getFilamentCardsGroup() |
public static function (): ?string |
Same (method form) |
$filamentCardsSearchKeywords |
public static array |
Extra search terms |
getFilamentCardsSearchKeywords() |
public static function (): array |
Same (method form) |
getNavigationBadge() |
Filament built-in | Read by discovery for the card badge |
getNavigationBadgeColor() |
Filament built-in | Read by discovery for the badge color |
$navigationLabel / $navigationIcon / $navigationSort / $navigationGroup |
Filament built-in | Used as defaults for label/icon/order/group |
Advanced — Dynamic Registration
Add cards from outside the class — useful for modular apps and packages. Call from a service provider's boot():
use App\Filament\Pages\ControlPanel;
use Harvirsidhu\FilamentCards\CardItem;
ControlPanel::addCards([
CardItem::make(UserManagement::class)
->label('User Accounts')
->icon('heroicon-o-users')
->description('Manage roles, permissions, and user accounts'),
]);
Advanced — Custom Discovery Filtering
Override shouldIncludeDiscoveredCard() for centralized inclusion logic that goes beyond exclude lists:
protected static function shouldIncludeDiscoveredCard(string $component): bool
{
if (! parent::shouldIncludeDiscoveredCard($component)) {
return false;
}
return $component !== BetaFeaturePage::class;
}
Full Example
Everything together — auto-discovery, manual cards, groups, conditional visibility, and an external link:
use Filament\Support\Enums\Alignment;
use Filament\Support\Enums\IconPosition;
use Filament\Support\Enums\IconSize;
use Harvirsidhu\FilamentCards\CardGroup;
use Harvirsidhu\FilamentCards\CardItem;
use Harvirsidhu\FilamentCards\Filament\Pages\CardsPage;
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 — Plugin Registration
Not required, but you can register the plugin in your panel provider for clarity:
use Harvirsidhu\FilamentCards\FilamentCardsPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugins([
FilamentCardsPlugin::make(),
]);
}
License
MIT — see License File.