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.
Architecture fit This is a leaf, presentational dependency: Blade components that render tab navigation and panels. It does not touch persistence, queuing, auth, or request lifecycle, so its architectural blast radius is near zero — it either renders correct markup or it doesn't, and the fallback is a local Blade partial. That is a genuinely low-risk position for a third-party package to occupy.
The architectural question that matters is not "does it fit Laravel" (it does, by construction) but "what is it actually substituting for." A tab set is roughly 60–120 lines of Blade plus a small amount of ARIA and keyboard handling. Adopting a dependency here is a trade of ~2 hours of local implementation against an unmaintained-package risk. The package's stated differentiators are accessible output, automatic active-state detection, and no build step / no front-end framework. Those are the claims to validate technically, because they are the only reason to prefer this over an in-house component:
role="tablist"/role="tab"/role="tabpanel", aria-selected, aria-controls/aria-labelledby wiring, roving tabindex, and arrow-key/Home/End keyboard navigation. If there is no JavaScript and no Alpine dependency, then either (a) it is CSS-only (radio-input or :target pattern), which is accessible but has known limitations with deep-linking and dynamic panels, or (b) it ships inline JS that the description is underselling. We need to read the compiled output before accepting the "accessible" claim as true.Net: architecturally compatible, but the value proposition rests on markup quality we have not inspected.
Integration feasibility
Mechanically trivial: composer require, provider auto-discovery, vendor:publish --tag=views, and use of the Blade component tag. Realistic estimate is under a day including a11y review, not a sprint.
The friction points are ordinary Blade-component integration concerns, not package defects:
tabs/tab will collide with any existing <x-tabs> in the app or in another UI package. Confirm the registration namespace and whether it is prefixed (e.g. x-sb-tabs) before install.--force will silently discard our customisations. This converts a dependency into a soft fork.sb- prefix). This is minor for a UI package and suggests the config surface is small, but it confirms the release notes are one line with no linked diff. Treat the upgrade as a config reconciliation task, not a hotfix: check config/, republish or merge, then php artisan config:clear before concluding anything about behaviour.Technical risk The dominant risk is vendor risk, not technical risk:
composer require.Key questions
composer.json constraints — which Laravel and PHP versions are supported, and is there a CI matrix?Stack fit Fits any Blade-rendering Laravel application, including Livewire pages. Does not fit Inertia/SPA front ends — the component renders server-side markup, so a React/Vue view layer gets nothing from it. If our roadmap includes an SPA migration, the useful life of this dependency is bounded by that migration, which further argues for keeping adoption shallow.
Build vs. buy. Given the scope (a tab set), the honest comparison is: an in-house Blade component is ~1–2 days for a developer who knows the ARIA tabs pattern, produces markup we fully control, carries zero supply-chain surface, and requires no fork-management process. The package wins only if its output is demonstrably better than what we would write and if we accept near-zero upstream support. I would frame this as a prototype-and-compare decision rather than a straight adopt: install it in a branch, render it, run an accessibility audit, and compare against a 100-line in-house component. Cost of that evaluation is hours.
Migration path Greenfield: install, pin the exact version, publish views, wire one tab set, audit. Existing custom tab partials: do not migrate all at once. Pick the highest-traffic tab set first, convert it, verify visual and behavioural parity, then convert the rest as touched — or don't convert at all. Because this is presentation-only, partial adoption is safe and reversible; there is no data or state migration.
Compatibility
Validate against our Laravel major before committing: check composer.json constraints, run the install in a branch on our actual framework version, and confirm the service provider boots and the components register. If our framework version is newer than the package's tested matrix, the practical risk is a Blade compilation or provider-registration break — cheap to detect in a branch, expensive to discover in production.
Sequencing
composer.json; do not allow floating constraints on a 0-star package.sb- prefix fix: check config/, republish or merge, php artisan config:clear, then php artisan config:cache as part of the normal release step.Maintenance Ongoing cost is low but non-zero, and it is concentrated in published-view drift rather than version bumps. Two commitments make this sustainable: (a) pin the version so upgrades are deliberate; (b) treat the published views as our code — reviewed, owned, and diffed against upstream stubs on each upgrade. If we cannot commit to (b), we should not publish the views; we should use the package's defaults and accept its styling.
The bus-factor risk should be answered with a standing exit plan: because the component is small and presentational, forking to app/View/Components is a contained, hours-long operation. Write that down. A dependency you can trivially exit is a dependency you can afford to take.
Support No upstream SLA exists and none should be assumed — 0 stars, single maintainer, one-line release notes. Support is entirely internal. The realistic ticket class is not breakage but "the tab isn't highlighting" or "the styling is off after the upgrade," both of which trace back to active-state assumptions or published-view drift. A short internal note covering the component name, the active-state mechanism, and the fork provenance will absorb most of that.
Scaling No runtime scaling concern. The component renders static markup at request time; cost is indistinguishable from any other Blade partial. It does not add queries, HTTP calls, or queue work. If a tab-heavy page ever becomes slow it will be because of the content inside the panels, not the tab wrapper. Also note the config surface appears to be minimal (a single naming fix in v1.0.1), so there is no boot-time configuration to reason about at scale.
Failure modes
aria-selected is not bound, we ship a WCAG regression while believing we improved accessibility. Onset is silent; discovery is an audit, a complaint, or a compliance finding. Mitigation: our own a11y test on adoption, not the vendor's claim.--force overwrite. An upgrade republish silently discards customisations, or our fork silently misses an upstream a11y fix. Mitigation: provenance note plus a diff step in the upgrade checklist.sb- prefix fix. Same class of issue as any Laravel package: an old published config file or a cached config masks the change. config:clear then config:cache in the release step.Ramp-up Very low. A Laravel developer reads the component API and ships a tab set in well under an hour. The non-obvious part is the accessibility verification, which needs someone who knows the ARIA tabs pattern — budget that expertise once, and it becomes a checklist item for every subsequent UI component we evaluate.
How can I help you explore Laravel packages today?