lara-zeus/accordion
Lara Zeus Accordion adds a simple accordion component to Laravel and Filament forms, letting you group fields into collapsible sections for cleaner, more organized layouts.
Install and pin the patch.
composer require lara-zeus/accordion:3.0.2
Use an exact version, not ^3.0. Eleven stars, zero dependents, one maintainer — every upgrade should be a visible diff you chose (composer update lara-zeus/accordion -W), not something a transitive resolve does for you on a Tuesday.
Confirm what you actually installed before writing code. Open vendor/lara-zeus/accordion/composer.json to read the Filament version constraint, and ls vendor/lara-zeus/accordion/src/ to see whether there's a class implementing Filament\Contracts\Plugin. A layout component usually needs no panel registration, but the lara-zeus family mixes both styles, so verify rather than assume. If there's no plugin class, don't touch your PanelProvider.
Read the Blade view first — it is the documentation.
vendor/lara-zeus/accordion/resources/views/ — the root markup, where zeus-accordion-btn / zeus-accordion-container land, and how open/closed state is driven (almost certainly Alpine x-data + x-show).vendor/lara-zeus/accordion/src/ — the component class, its fluent setters (heading(), icon(), schema(), whatever the open/collapse method is actually called), and the namespace you'll import.make() signature only. Treat it as a starting point, not a contract.First use case: the long Resource form. Top half is what people touch daily; bottom half is metadata nobody opens on a normal day.
use LaraZeus\Accordion\...\Accordion; // confirm namespace from src/
public static function form(Form $form): Form
{
return $form->schema([
TextInput::make('title')->required(),
TextInput::make('slug')->required(),
Accordion::make('seo')
->heading('SEO & metadata')
->icon('heroicon-o-magnifying-glass')
->schema([
Textarea::make('meta_description')->maxLength(160),
TextInput::make('canonical_url')->url(),
]),
Accordion::make('advanced')
->heading('Advanced')
->icon('heroicon-o-cog-6-tooth')
->schema([
Select::make('template')->options([...]),
Toggle::make('is_indexable')->default(true),
]),
]);
}
Ship one screen. Verify it renders, verify the collapsed fields still save, then widen adoption.
Wrap construction in one helper, always. The entire point of a dependency here is "one shared implementation instead of three divergent ones." If you're hand-writing Accordion::make('x')->heading('Y')->schema($fields) in nine places with drifting conventions, you've paid for a package and gotten none of the benefit.
// app/Filament/Support/Fields.php
class Fields
{
public static function metaSection(array $schema, bool $open = false): mixed
{
return Accordion::make('meta_'.md5(json_encode(array_keys($schema))))
->heading(__('admin.sections.meta'))
->icon('heroicon-o-adjustments-horizontal')
->open($open) // confirm the exact method name against src/
->schema($schema);
}
}
Now heading copy, icon, default open state, and future theming all live in one file, and "change the icon everywhere" is one line.
Open on create, collapse on edit. Progressive disclosure that pays for itself immediately:
->open($this->getRecord() === null)
On create the user needs to see everything once; on edit the record is populated and the secondary fields are noise. Test this against Livewire re-renders specifically — see Gotchas.
Nest inside the layout you already have. These are schema components, so they compose with Section, Grid, Tabs, Fieldset. The hierarchy that stays readable:
Tabs for top-level navigation between genuinely different concernsSection for visual grouping inside a tabAccordion for secondary content inside a sectionReserve collapse for content that is genuinely optional on most visits. An accordion costs a click; a section doesn't.
Not just forms. A Filament schema doesn't have to be inputs — Placeholder, ViewField, TextEntry all work inside. That makes this usable on read-only detail pages and settings panels. Its highest-value home is Action modals: modals are height-constrained, and collapsing half the form is often the difference between a modal that fits and one that scrolls forever.
Theme through the 3.0.2 hooks, in exactly one file.
/* resources/css/zeus-accordion.css — the only file allowed to touch these */
.zeus-accordion-btn { /* trigger: padding, weight, hover */ }
.zeus-accordion-container { /* panel: spacing, border */ }
Two rules keep this cheap over two years:
zeus-accordion-btn* and zeus-accordion-container*. The moment you write a descendant selector against the package's markup you've reintroduced fork debt — precisely what 3.0.2 exists to remove.->extraAttributes(['class' => 'app-accordion'])) so CSS doesn't leak into Filament surfaces you didn't mean to restyle.The -* modifier suffixes aren't enumerated in the release notes. Treat them as unconfirmed until you've inspected the rendered DOM; depend only on the two base classes until then.
Test the save, not the pixels.
livewire(CreatePost::class)
->fillForm(['title' => 'x', 'slug' => 'x', 'meta_description' => 'y'])
->call('create')
->assertHasNoFormErrors();
$this->assertDatabaseHas('posts', ['meta_description' => 'y']);
Add one visual check — a screenshot test, or a manual checklist item — covering collapsed and expanded states. A patch bump silently restyling a panel is the realistic failure mode, and a functional test cannot see it.
Collapsed is not absent. Fields inside a collapsed section are still mounted, still validated, still submitted. This is the #1 surprise: client-side required fires on a field the user can't see, and you get "the form won't submit and I can't see why." Mitigations:
Flip side: it's a feature. You can collapse fields without touching validation rules or $fillable. Nothing is conditionally present server-side — it's purely visual.
State across Livewire re-renders. Filament re-renders the schema constantly. Whatever drives open/closed is almost certainly Alpine state, so test explicitly that a section you opened doesn't snap shut after typing into a live() field or a ->reactive() dependent. That combination — live() fields inside a collapsed section — is the one that breaks.
Version alignment with Filament. Already on its third major line, this package tracks Filament's majors. Before adopting, confirm which Filament version 3.x targets and match it. If you're behind on Filament you're probably behind here too, and the fix is a Filament upgrade you hadn't planned. Keep any 2.x → 3.x move in its own commit, separate from theming, so a breakage tells you which change caused it.
Accessibility ownership is undocumented. The release notes say nothing about aria-expanded, aria-controls, or keyboard handling. Open a panel, Tab to a trigger, hit Enter and Space, inspect the DOM. If the ARIA isn't there, it's yours to add — and the cheapest place is your single wrapper component, not nine call sites. Verify once, write down the answer, stop re-litigating.
Deep nesting is a trap. Accordion inside accordion inside a tab is technically fine and experientially awful — the user has to remember click history to get back. One level of collapse. If you want two, the real problem is too many fields; the fix is a tab or a wizard step, not more disclosure.
Custom Alpine inside accordion content. If you nest your own x-data component inside a panel, scope directives carefully. The collapse parent likely holds state with generically-named properties; a local x-data can shadow and clobber them. Isolate your component and use uniquely-named properties.
Debugging checklist, in order:
zeus-accordion-btn element and read its Alpine attributes. That tells you the mechanism in thirty seconds.vendor/ (or publish the views) — the Blade shows every variable the component passes.Exit cost is low — keep it that way. The realistic risk isn't a bug, it's abandonment. Protect structurally: never reference zeus-accordion-* selectors outside your one stylesheet, and route every construction through your Fields::metaSection() wrapper. Do that and swapping in native <details>/<summary> or an in-house Alpine toggle is an afternoon.
When not to use it at all. A single FAQ block on a marketing page doesn't need this. Native <details>/<summary> is zero dependencies, keyboard-accessible by default, and works without JS. The package earns its place when you're inside a Filament schema and need collapse to cooperate with form state, layout, and theming — that integration value is the only reason to take on a third-party lifecycle here.
How can I help you explore Laravel packages today?