{"id":51040161,"url":"https://github.com/mustafakhaleddev/filament-responsive-table","last_synced_at":"2026-06-22T10:01:46.312Z","repository":{"id":363209526,"uuid":"1217522516","full_name":"mustafakhaleddev/filament-responsive-table","owner":"mustafakhaleddev","description":"Filament Responsive Table","archived":false,"fork":false,"pushed_at":"2026-04-22T01:11:41.000Z","size":10,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-08T00:06:54.086Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mustafakhaleddev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-22T01:10:37.000Z","updated_at":"2026-04-22T01:11:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mustafakhaleddev/filament-responsive-table","commit_stats":null,"previous_names":["mustafakhaleddev/filament-responsive-table"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/mustafakhaleddev/filament-responsive-table","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mustafakhaleddev%2Ffilament-responsive-table","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mustafakhaleddev%2Ffilament-responsive-table/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mustafakhaleddev%2Ffilament-responsive-table/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mustafakhaleddev%2Ffilament-responsive-table/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mustafakhaleddev","download_url":"https://codeload.github.com/mustafakhaleddev/filament-responsive-table/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mustafakhaleddev%2Ffilament-responsive-table/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34643624,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-22T02:00:06.391Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-22T10:01:45.735Z","updated_at":"2026-06-22T10:01:46.306Z","avatar_url":"https://github.com/mustafakhaleddev.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Filament Responsive Table\n\nRender a Filament list table as stacked cards below a configurable Tailwind breakpoint. Above the breakpoint nothing changes — the native Filament table renders untouched. Below the breakpoint each row becomes a card with column labels, values, and the row's record actions in the footer.\n\nThe same `table()` definition drives both views — no duplicate column lists, no second source of truth.\n\n## Requirements\n\n- PHP 8.2+\n- Laravel 11+ / 13\n- Filament 4 or 5\n\n## Features\n\n- **Breakpoint-driven** — pick `sm`, `md`, `lg`, `xl`, or `2xl`; below it the table becomes cards, above it the native table renders\n- **Zero duplication** — columns and record actions come straight from your existing `table()` method\n- **Two-column label/value grid** inside each card, matching mobile UX expectations\n- **Optional per-card title** resolved from the record\n- **Optional bulk-selection checkbox** on each card so bulk actions still work on mobile\n- **Optional record actions footer** — stack the row's actions at the bottom of each card\n- **Column filtering** — `only()` / `except()` hide columns in cards without affecting the desktop table\n- **Custom card Blade view** — opt out of the default template whenever you need it\n- **Plugin-level defaults** — set a single default breakpoint for every responsive list page in a panel\n- **Three-level configuration cascade** — page overrides plugin overrides config file\n- **Dark mode support**\n\n## Installation\n\n```bash\ncomposer require wezlo/filament-responsive-table\n```\n\nOptionally register the plugin in your Panel Provider for global defaults:\n\n```php\nuse Wezlo\\FilamentResponsiveTable\\FilamentResponsiveTablePlugin;\n\n-\u003eplugins([\n    FilamentResponsiveTablePlugin::make()\n        -\u003edefaultBreakpoint('md'),\n])\n```\n\nOptionally publish the config:\n\n```bash\nphp artisan vendor:publish --tag=filament-responsive-table-config\n```\n\n### Theme Source (Tailwind v4)\n\nThe package's Blade views use Tailwind utility classes. For Tailwind to detect them during your app's build, add the package's views as a `@source` in your Filament custom theme CSS file (usually `resources/css/filament/admin/theme.css`):\n\n```css\n@import '../../../../vendor/filament/filament/resources/css/theme.css';\n\n@source '../../../../vendor/wezlo/filament-responsive-table/resources/views/**/*';\n\n@custom-variant dark (\u0026:where(.dark, .dark *));\n```\n\nIf you don't have a custom theme yet, create one:\n\n```bash\nphp artisan make:filament-theme\n```\n\nThen rebuild assets:\n\n```bash\nnpm run build\n```\n\n## Quick Start\n\nAdd the `HasResponsiveTable` trait to your resource's `ListRecords` page and declare a `$responsiveBreakpoint` property to pick the breakpoint:\n\n```php\nuse Filament\\Resources\\Pages\\ListRecords;\nuse Wezlo\\FilamentResponsiveTable\\Concerns\\HasResponsiveTable;\n\nclass ListUsers extends ListRecords\n{\n    use HasResponsiveTable;\n\n    protected static string $resource = UserResource::class;\n\n    public ?string $responsiveBreakpoint = 'md';\n}\n```\n\nThat's it. At viewports `\u003c md` (768px) every row collapses into a card showing each column's label on the left and its rendered value on the right. Record actions stack in a footer below.\n\n\u003e The trait does not declare `$responsiveBreakpoint` itself (to avoid PHP trait/class property conflicts), so you declare it on your list page with whatever visibility and default you like — `public`, `protected`, `?string`, or a non-nullable `string` with a default value all work.\n\n## Configuration API\n\nFor anything beyond the breakpoint shortcut, implement the `responsiveTable()` method. The configuration from the method wins over the `$responsiveBreakpoint` property.\n\n```php\nuse Wezlo\\FilamentResponsiveTable\\Concerns\\HasResponsiveTable;\nuse Wezlo\\FilamentResponsiveTable\\ResponsiveTableConfiguration;\n\nclass ListUsers extends ListRecords\n{\n    use HasResponsiveTable;\n\n    public function responsiveTable(ResponsiveTableConfiguration $config): ResponsiveTableConfiguration\n    {\n        return $config\n            -\u003ebreakpoint('lg')\n            -\u003eexcept(['id', 'created_at'])\n            -\u003ecardTitle(fn ($record) =\u003e $record-\u003ename)\n            -\u003eshowRecordActions()\n            -\u003eshowBulkSelection();\n    }\n}\n```\n\n### Configuration Reference\n\n| Method | Signature | Description |\n|---|---|---|\n| `breakpoint(string)` | `'sm' \\| 'md' \\| 'lg' \\| 'xl' \\| '2xl'` | Below this Tailwind breakpoint, rows render as cards. Throws on unknown values. |\n| `only(array)` | `array\u003cstring\u003e` | Keep only these column names in cards. Desktop table is untouched. |\n| `except(array)` | `array\u003cstring\u003e` | Hide these column names from cards. Desktop table is untouched. |\n| `cardTitle(Closure)` | `fn (Model $record): string\\|Htmlable\\|null` | Resolve a per-card header title from the record. |\n| `showRecordActions(bool)` | `bool` (default `true`) | Render the table's record actions in each card's footer. |\n| `showBulkSelection(bool)` | `bool` (default `false`) | Show the bulk-selection checkbox on each card. |\n| `cardView(string)` | `string` | Override the default card Blade template. |\n\n### Column Filtering\n\n`only()` and `except()` match against `Column::getName()` — the first argument to `TextColumn::make('name')`, `IconColumn::make('status')`, etc. Nested relationship columns like `client.name` match by that exact name.\n\n```php\n$config-\u003eonly(['name', 'email', 'status']);\n// or\n$config-\u003eexcept(['id', 'created_at', 'updated_at', 'deleted_at']);\n```\n\n### Card Title\n\nWithout `cardTitle()`, the card has no header bar — it's just label/value rows and the action footer. Setting it renders a header strip with the title text (and the bulk checkbox if enabled).\n\n```php\n$config-\u003ecardTitle(fn ($record) =\u003e \"#{$record-\u003einvoice_number}\");\n$config-\u003ecardTitle(fn ($record) =\u003e new HtmlString(\"\u003cstrong\u003e{$record-\u003ename}\u003c/strong\u003e\"));\n```\n\n### Custom Card View\n\nPoint to your own Blade view if the default layout doesn't fit:\n\n```php\n$config-\u003ecardView('users.mobile-card');\n```\n\nThe view receives three variables:\n\n| Variable | Type | Description |\n|---|---|---|\n| `$record` | `Model` | The Eloquent record for this card |\n| `$columns` | `array\u003cColumn\u003e` | The visible card columns (after `only`/`except`) |\n| `$config` | `ResponsiveTableConfiguration` | The resolved configuration |\n\nYou can use `$this-\u003egetResponsiveTableRecordActions($record)` inside the view to get the cloned, record-bound, visibility-filtered actions array.\n\n## Plugin Configuration\n\nRegister the plugin in your Panel Provider to set defaults for all responsive list pages in that panel:\n\n```php\nuse Wezlo\\FilamentResponsiveTable\\FilamentResponsiveTablePlugin;\n\npublic function panel(Panel $panel): Panel\n{\n    return $panel\n        -\u003eplugins([\n            FilamentResponsiveTablePlugin::make()\n                -\u003edefaultBreakpoint('md')\n                -\u003edefaultShowRecordActions(true)\n                -\u003edefaultShowBulkSelection(false),\n        ]);\n}\n```\n\n| Method | Type | Default | Description |\n|---|---|---|---|\n| `defaultBreakpoint(string)` | `string` | `null` | Default breakpoint when no page sets one |\n| `defaultShowRecordActions(bool)` | `bool` | `null` | Default visibility of the actions footer |\n| `defaultShowBulkSelection(bool)` | `bool` | `null` | Default visibility of the bulk checkbox |\n\n## Configuration Cascade\n\nEach setting resolves through a four-level cascade:\n\n1. **`responsiveTable()` method** on the `ListRecords` page (highest priority)\n2. **`$responsiveBreakpoint` property** on the `ListRecords` page (breakpoint only)\n3. **Plugin defaults** on `FilamentResponsiveTablePlugin` in the Panel Provider\n4. **Config file** — `config/filament-responsive-table.php` (lowest priority)\n\nThe method always wins over the property, which wins over plugin defaults, which win over the config file.\n\n## Default Config File\n\n```php\n// config/filament-responsive-table.php\nreturn [\n    'breakpoint' =\u003e 'md',\n    'show_record_actions' =\u003e true,\n    'show_bulk_selection' =\u003e false,\n];\n```\n\n## Full Example\n\n```php\nuse Filament\\Resources\\Pages\\ListRecords;\nuse Wezlo\\FilamentResponsiveTable\\Concerns\\HasResponsiveTable;\nuse Wezlo\\FilamentResponsiveTable\\ResponsiveTableConfiguration;\n\nclass ListOrders extends ListRecords\n{\n    use HasResponsiveTable;\n\n    protected static string $resource = OrderResource::class;\n\n    public function responsiveTable(ResponsiveTableConfiguration $config): ResponsiveTableConfiguration\n    {\n        return $config\n            -\u003ebreakpoint('lg')\n            -\u003eexcept(['id'])\n            -\u003ecardTitle(fn ($record) =\u003e \"#{$record-\u003enumber}\")\n            -\u003eshowRecordActions()\n            -\u003eshowBulkSelection();\n    }\n}\n```\n\nThe resource's `table()` method stays unchanged — columns, filters, search, header actions, record actions, and bulk actions all carry over to the card view automatically.\n\n## How It Works\n\n- The `HasResponsiveTable` trait overrides `content()` on the `ListRecords` page to render a single wrapper view that contains **both** the native Filament table (via `$this-\u003egetTable()-\u003erender()`) and a card stack generated from the same columns.\n- The wrapper `\u003cdiv\u003e` carries `data-breakpoint=\"\u003cbp\u003e\"`. A small shipped stylesheet has static `@media` rules — at the configured breakpoint it hides the table and shows the cards, and vice-versa above it. Because the rules are static CSS (not Tailwind utilities), Tailwind's JIT scan isn't required for visibility toggling.\n- Each card pulls its columns from `$this-\u003egetTable()-\u003egetVisibleColumns()`, then applies `only`/`except`. Columns are cloned per record (`$column-\u003egetClone()-\u003erecord($record)`) so the same render pipeline used by the desktop table — badges, icons, date formatting, images — produces the card values.\n- Record actions are cloned per record (`$action-\u003egetClone()-\u003erecord($record)`) and filtered by `isHidden()`, mirroring the pattern in Filament's own table Blade view.\n- The desktop table is Filament's native `Table::render()` output — search, filters, sorting, pagination, bulk actions, and row actions all work exactly as before.\n\n## CSS Classes\n\nAll elements use `fi-responsive-table-*` prefixed classes for targeted styling:\n\n| Class | Element |\n|---|---|\n| `fi-responsive-table` | Root wrapper (carries `data-breakpoint`) |\n| `fi-responsive-table-desktop` | Wraps Filament's native table |\n| `fi-responsive-table-cards` | Wraps the card stack |\n| `fi-responsive-table-cards-list` | Inner flex container for cards |\n| `fi-responsive-table-card` | Individual card |\n| `fi-responsive-table-card-header` | Card title + optional checkbox bar |\n| `fi-responsive-table-card-title` | Title text |\n| `fi-responsive-table-card-checkbox` | Bulk-selection checkbox |\n| `fi-responsive-table-card-body` | `\u003cdl\u003e` grid of label/value pairs |\n| `fi-responsive-table-card-field` | One label/value pair (uses `display: contents`) |\n| `fi-responsive-table-card-field-label` | Column label (`\u003cdt\u003e`) |\n| `fi-responsive-table-card-field-value` | Rendered column value (`\u003cdd\u003e`) |\n| `fi-responsive-table-card-footer` | Record-actions footer |\n\nOverride any of these in your theme CSS to customize the card appearance.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmustafakhaleddev%2Ffilament-responsive-table","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmustafakhaleddev%2Ffilament-responsive-table","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmustafakhaleddev%2Ffilament-responsive-table/lists"}