mrshanebarron/tabs
mrshanebarron/tabs is a Laravel package for building tabbed interfaces in Blade views. Define tabs in PHP, render accessible markup, and switch panels without custom JavaScript. Lightweight, configurable, and easy to drop into any Laravel app.
composer require mrshanebarron/tabs
If your app disables discovery, add the provider to config/app.php manually.vendor/mrshanebarron/tabs/src and vendor/mrshanebarron/tabs/resources/views before writing markup. Component names, slot names, and the shape of the tab definition API are the things you need to get right on day one, and they're five seconds away in the vendor tree (php artisan vendor:publish --list will also show the available publish tags).php artisan vendor:publish --provider="MrShaneBarron\\Tabs\\TabsServiceProvider" --tag="views"
php artisan vendor:publish --provider="MrShaneBarron\\Tabs\\TabsServiceProvider" --tag="config"
php artisan config:clear && php artisan view:clear
v1.0.1 fixed config file naming with the sb- prefix — expect config/sb-tabs.php, and read it with config('sb-tabs.key'). If you're coming from 1.0.0, check config/ for a stale unprefixed file and delete or merge it rather than leaving both./settings/profile before you spread the pattern to other pages.@include (or <x-...>) a partial per panel so each panel stays independently testable and reusable.resources/views/components/app/tabs.blade.php that composes the package's components with your project's classes and defaults. Every page then uses <x-app.tabs>; when the package changes its markup or you restyle, you edit one file instead of forty pages.resources/views/vendor/tabs. From that moment the package's own views are dead to you — which is fine, and is the intended extension point — but it also means upstream template fixes won't reach you. Diff the vendor views against yours on every upgrade./settings/profile, /settings/password), and let the component resolve it. Reach for an explicit override only for genuinely non-URL state.?tab=billing via request()->query('tab'), validate it against a whitelist, and fall back to a default. That makes tabs shareable and back-button friendly without touching the package.<form>, many panels. Keep the entire tab set inside a single form element so hidden panels still submit their inputs. Never conditionally @if a panel away — a disabled or unrendered panel silently drops its fields on POST.assertSee on the active class or aria-selected="true") plus the presence of each panel's content catches the realistic regressions. Add a test per tab route so a rename can't quietly break active detection.role="tablist", role="tab", aria-selected, and aria-controls wired to the panels. If you publish and restyle the views, preserve those attributes — they are the first thing lost in a markup rewrite.sb- prefix is part of the config key. config/sb-tabs.php is read as config('sb-tabs.default'), not config('tabs.default'). After the 1.0.1 rename, grep for stale accessors:
grep -rn "config('tabs" app/ config/ resources/ tests/
vendor:publish never overwrites. Your published config and views are frozen at the version you published them. After an upgrade, compare the package stubs against your copies and merge new keys manually — missing config keys usually surface as silent null defaults, not exceptions.php artisan config:clear; if markup doesn't change after editing a published view, run php artisan view:clear. In production, php artisan config:cache and php artisan view:cache belong in the deploy script after config and views are in place.@foreach) produces duplicate id/aria-controls pairs, and the wrong panel toggles. If the package doesn't accept an id/name prefix, pass the loop index or model key into a wrapper and namespace your own ids./settings matches /settings/profile under a loose prefix check. Prefer exact route-name matching (request()->routeIs('settings.profile')) over URL-prefix heuristics, and test the sibling routes explicitly.@error checks per panel).wire:navigate, or an SPA-style router, panels can end up in a stale state after a partial page swap. Re-key the component or drive active state from a reactive property rather than assuming a fresh boot.php artisan tinker then config('sb-tabs') to dump the resolved array; app()->make(...) to verify the container binding; php artisan vendor:publish --list to confirm tags (a tag mismatch is the #1 reason "the config file never appeared"); check bootstrap/cache/config.php exists if you suspect caching.vendor/. All customisation belongs in published views, the published sb-tabs.php config, or your own wrapper component. Vendor edits disappear on the next composer update.How can I help you explore Laravel packages today?