Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Tab Layout Plugin Laravel Package

solution-forest/tab-layout-plugin

Add tabbed layouts to your Laravel Filament forms and pages with this plugin from Solution Forest. Organize fields into clean, navigable tabs using a simple schema API, keeping complex admin forms readable and easy to build.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Confirm compatibility before anything else. This is a small (27★) single-maintainer plugin, and Filament's schema/form APIs changed shape between v2, v3, and v4. Check the composer.json of the tagged release you're installing against your Filament constraint, and check the CHANGELOG/commit dates rather than trusting the "last release" date alone — a 2026-02-02 tag on a low-traffic repo can still be a version-bump-only commit.

composer require solution-forest/tab-layout-plugin

2. Register the plugin in your panel provider if it ships a Plugin class:

use SolutionForest\TabLayoutPlugin\TabLayoutPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        ->plugins([
            TabLayoutPlugin::make(),
        ]);
}

If there's no plugin class, skip this — many layout-only plugins are pure Blade/component packages with nothing to register. Publish config/views only if the README says to:

php artisan vendor:publish --tag="tab-layout-plugin-config"
php artisan vendor:publish --tag="tab-layout-plugin-views"

3. Read these three things first, in order:

  • README.md — for the canonical usage snippet. Copy it verbatim into a scratch page before adapting.
  • src/ — the exact fully-qualified class names and, critically, which base class the tab component extends (Filament\Forms\Components\Tabs vs Filament\Schemas\Components\Tabs vs a custom view component). This tells you instantly whether it's a form component, an infolist component, or a page-level layout.
  • resources/views/ — to see whether it's wrapping Filament's own tab Blade or rolling its own markup. This determines how much styling/theming work you inherit.

4. First use case: tab a resource form. The lowest-risk adoption is dropping the tab component into an existing Resource::form() schema and moving a handful of rarely-touched fields into a second tab:

public static function form(Form $form): Form
{
    return $form->schema([
        Tabs::make('Listing')
            ->tabs([
                Tabs\Tab::make('Details')
                    ->schema([
                        TextInput::make('name')->required(),
                        TextInput::make('slug'),
                    ]),
                Tabs\Tab::make('SEO')
                    ->schema([
                        TextInput::make('meta_title'),
                        Textarea::make('meta_description'),
                    ]),
            ])
            ->columnSpanFull(),
    ]);
}

Verify the form still saves and validates before going further — that single round-trip catches 90% of state-path and hydration issues.

Implementation Patterns

Form-schema tabs (most common). Tabs are a layout, not a data structure. Each Tab::make() gets its own ->schema([...]), and all child fields still write to the parent form's state. Use ->columns() on the tab to control its internal grid independently of the surrounding form.

Page-level tabs via a custom page. For "Overview / Activity / Settings" style pages, the pattern is a custom Filament page whose view delegates to a Blade component, or a ManageRelatedRecords-style tab strip where each tab renders a table or widget:

class ViewCustomer extends Page
{
    protected static string $view = 'filament.pages.view-customer';
    protected static string $resource = CustomerResource::class;

    public function getTabs(): array
    {
        return [
            'overview' => OverviewTab::make(),
            'orders'   => OrdersTab::make(),
        ];
    }
}

Keep each tab's content in its own Livewire component (or its own Blade partial) rather than inlining everything into one giant page class — this is the single biggest maintainability win, and it makes each tab independently testable.

Tabs as navigation, not layout. A pattern that ages well: derive the tab list from a config array or enum so routes, labels, and icons stay in sync with permissions:

->tabs(collect(CustomerTab::cases())
    ->filter(fn ($t) => auth()->user()->can($t->permission()))
    ->map(fn ($t) => Tabs\Tab::make($t->label())
        ->icon($t->icon())
        ->schema($t->schema()))
    ->all())

Badges and counts. Tab labels accept a badge/icon in Filament's API; wire the count to a computed property rather than querying inside the closure on every render.

Conditional tabs. ->visible() on the tab component hides the whole strip; ->hidden() on individual tabs removes them. Prefer removing tabs to disabling them — an empty disabled tab is a dead end for users.

Integration with widgets. For dashboard-style tabbed dashboards, render widgets inside each tab and give each tab a stable wire:key-friendly id so Livewire doesn't swap DOM nodes between tabs.

Lazy loading. If a tab holds expensive content (tables, charts, related-record counts), gate it behind ->lazy() where available, or render a placeholder and hydrate on tab activation. This matters most on Edit pages that would otherwise run four relation-count queries on first paint.

Gotchas and Tips

Namespace collision with Filament core. Filament ships its own Tabs / Tab classes. If you use the plugin's tab classes and also need core's, alias one:

use SolutionForest\TabLayoutPlugin\Components\Tabs as LayoutTabs;

A Class not found or "method does not exist" error after installing is almost always this, or an autoload issue — run composer dump-autoload and clear cached views/config.

Version drift is the real risk. With zero dependents and a single maintainer, you are the QA team. Pin the version in composer.json ("^1.0", not "*"), and add a smoke test that boots the panel and asserts the page renders 200. When you upgrade Filament, upgrade this in the same PR — never let a Filament major bump land separately.

State path bugs. If fields inside a tab fail to save, check whether the tab is nested inside a Repeater, Builder, or another tab. Nested schemas can drop the parent's state path prefix. Debug by dumping $livewire->form->getState() in a save() override before the model is filled.

wire:key and re-render flicker. Dynamic tab sets (permission-filtered, or keyed by a select value) will lose focus and reset scroll if the tab array's order changes between renders. Sort deterministically and set explicit keys.

Styling gaps. If the plugin uses its own Blade rather than Filament's, expect visual drift after a Filament theme update. The fix is to publish the views and diff them against vendor/filament/.../resources/views/components/tabs, or override via a custom theme's tailwind.config.js content paths. Register the published views path so Tailwind actually compiles the classes.

Anchors and deep links. Filament's tabs manage active state in Alpine/Livewire state, not the URL. If you need linkable tabs (support tickets, bookmarks), you must add a query-string hook yourself — #[Url] on a public $activeTab property, synced to the tab component.

Testing. Use Livewire::test() against the page and assertSee() on a field label unique to the second tab — this confirms the tab actually mounted. Add ->call('$set', 'activeTab', 'seo') style assertions if tab switching matters to your acceptance criteria.

Extension points to prefer. If you need behavior the plugin doesn't expose, prefer wrapping it in your own small component or Blade partial over forking. Forking a 27★ repo means you own every upstream fix; wrapping means you can drop it later.

Have an exit plan. The functionality overlaps heavily with Filament's built-in Tabs, which is free, tested, and versioned with the framework. Before adopting, write the same screen using core Tabs and compare. If core is sufficient, use core — reserve this plugin for the specific layout behavior it adds that core lacks, and document that reason in a code comment so the next developer doesn't wonder why the dependency exists.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo