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

Nova Tabs Laravel Package

shuvroroy/nova-tabs

A Laravel Nova package that organizes resource fields into clean, navigable tabs, keeping complex forms tidy and easier to scan. Add tab panels to your Nova resources with simple method chaining.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and pin the version. Nova is a proprietary, licensed dependency and this package's last tagged release is from November 2023, so the version constraint is part of your compatibility contract rather than an incidental detail.

composer require shuvroroy/nova-tabs:1.1.1

Pin it exactly, or at most with a tight ~1.1.1 range, and commit composer.lock. There is no published config file, no migration, and no publishable assets — the entire integration surface is a handful of PHP classes plus a compiled Vue bundle registered through Nova's script API. That means a floating constraint buys you nothing but surprise breakage the next time someone runs composer update after a Nova bump.

Where to look first. Skip the README-as-manual approach and read vendor/shuvroroy/nova-tabs/src/ directly — at this size the source is the documentation. Three things matter:

  1. The Tabs and Tab classes. Confirm the exact signatures — whether fields are the second positional argument to Tab::make(), and which fluent modifiers exist (fullWidth(), and anything else). The README shows a shape; the class shows the truth.
  2. The service provider. Note how it calls Nova::script() and what compiled bundle it points at. That filename is what you look for in the browser Network tab when tabs render but refuse to switch.
  3. The Vue component. Determine whether it is purely a presentational wrapper (v-show/v-if over field slots) or whether it intercepts validation state. That single detail determines your entire risk profile — see Gotchas.

First use case. Pick a resource that is genuinely painful to scroll — 25+ fields on the create/update form — and one that is not business-critical. Grouping tends to fall out naturally by task:

use ShuvroRoy\NovaTabs\Tab;
use ShuvroRoy\NovaTabs\Tabs;

public function fields(NovaRequest $request): array
{
    return [
        Tabs::make()->withTabs([
            Tab::make('General', [
                ID::make()->sortable(),
                Text::make('Title', 'name')->rules('required'),
                Slug::make('Slug', 'slug')->rules('required'),
                Select::make('Status')->options([...]),
            ]),
            Tab::make('Content', [
                Textarea::make('Excerpt'),
                Markdown::make('Body'),
            ]),
            Tab::make('Relations', [
                BelongsTo::make('Author', 'author', User::class),
                BelongsToMany::make('Tags'),
            ])->fullWidth(),
            Tab::make('Meta', [
                DateTime::make('Published At')->onlyOnDetail(),
            ]),
        ]),
    ];
}

Load the create form and the detail page immediately after wiring it up. They are different Nova rendering paths, and it is common for one to be correct while the other has a layout or visibility problem.

Introduce an adapter on day one. Before a second resource adopts tabs, add a single wrapper so the dependency is swappable:

namespace App\Nova\Fields;

class TabsField
{
    public static function make(): \ShuvroRoy\NovaTabs\Tabs
    {
        // your defaults, ordering conventions, fullWidth() rules
    }
}

This converts "we depend on a low-traffic third-party package" into "we depend on a one-file adapter." Replacing it with a maintained alternative or an in-house implementation later becomes a single-file change instead of a resource-by-resource refactor.

Implementation Patterns

Tabs must be the sole top-level entry in fields(). The component owns the layout of its children; anything returned as a sibling renders outside the tab strip and looks orphaned. This is the first thing to enforce in code review. If you need something outside the tabs — a status banner, a computed read-only value — give it its own leading tab (usually "Overview") rather than sitting beside Tabs::make().

Extract each tab into a method. Four tabs of eight fields each is a 40-line array literal that nobody wants to review. Split it:

public function fields(NovaRequest $request): array
{
    return [
        TabsField::make()->withTabs([
            $this->generalTab($request),
            $this->contentTab($request),
            $this->relationsTab($request),
        ]),
    ];
}

protected function generalTab(NovaRequest $request): Tab
{
    return Tab::make('General', [
        Text::make('Title')->rules('required'),
        Select::make('Status')->options(Status::options()),
    ]);
}

This also makes shared field shapes portable across resources via a trait, which keeps validation rules and option lists in exactly one place.

Ordering is array order, full stop. There is no ->order() or ->priority(). Moving a tab means moving the literal. That is fine, but it makes readability the only ordering mechanism — keep arrays flat and obviously sequenced, and pull any match/if branching out into methods rather than inlining it between elements.

Keep relationship fields in their own tab and make it fullWidth(). BelongsToMany, HasMany, and MorphMany render nested index tables that get cramped and horizontally scrollable in a half-width column. ->fullWidth() on that tab prevents the squeeze on smaller admin monitors.

Use onlyOnDetail() inside a Meta tab for expensive or rarely-edited fields. Nova resolves every field returned from fields() on form requests, including resolveUsing callbacks and heavy accessors. Pushing those into a detail-only Meta tab is a cheap latency win on resources with computed attributes.

Standardize the taxonomy across resources. Once three or four resources have tabs, divergent naming ("General" vs "Overview" vs "Details") reintroduces the exact cognitive load the feature was meant to remove. Pick a small vocabulary — General / Content / Relations / Meta covers most CRUD — write it in the internal README, and apply it everywhere.

Conditional fields work, but verify them explicitly. dependsOn, hideWhenCreating(), and friends behave normally because the package does not intercept them. The interaction to watch is visibility: a field that becomes visible due to a dependency on an inactive tab produces no visible feedback, because the change happened off-screen. Where a dependency exists, keep both fields in the same tab.

Required fields go in the first tab — treat this as a convention, not a preference. See Gotchas. If the convention is unacceptable for a particular resource, the alternative is a small fork that auto-selects the first tab containing an invalid field on a 422 response.

Gotchas and Tips

Hidden validation errors are the real risk — not the layout. If a required field sits in an inactive tab and the request fails validation, Nova may render the error markup into the hidden tab, so the form appears to silently refuse to save. The package is presentational and does not implement error routing; do not assume this is handled. Prove it during the pilot by submitting a form with a required field in tab three left blank.

Give whoever triages admin tickets a one-line script so this never becomes a mystery: DevTools → Network → open the 422 response → read the body. It names every failing field regardless of which tab is active. That turns a half-hour "is the admin broken?" investigation into a thirty-second lookup.

Tab names colliding with relationship names. v1.1.1 fixed a bug where a tab field's slug collided with a relationship field of the same name. Stay on ≥1.1.1, and even then avoid naming a tab exactly after a relationship on the same resource — a "Tags" tab next to a Tags relationship is cheap insurance to rename.

Never nest Tabs inside Tab. It is not supported. If you need sub-grouping, use Nova's native Panel::make() inside the tab's field array; that composes cleanly and is the sanctioned escape hatch.

Expect asset bundle conflicts. If another custom Nova tool or card already ships JavaScript into the admin, test tabs on a resource in that area during the pilot rather than on a clean one. Symptoms are tabs that render but do not switch, or a console error about duplicate Vue component registration. Debug in the browser console first — the compiled bundle filename is visible in the Network tab.

Nova majors are gated changes. The package registers a compiled Vue bundle against Nova's extension API, which moves between majors. Put "regression-test tabbed resources" as a standing item in your Nova upgrade checklist and budget a small number of engineering days per major to validate or patch. MIT licensing is the mitigation: a fork is legally and practically viable, so the downside is bounded.

Accessibility is unverified. Do not assume keyboard tab navigation or screen-reader semantics exist. If a tabbed surface carries accessibility obligations, test keyboard-only navigation and a screen reader before rollout, and be prepared to leave that resource untabbed.

Abandonment is a contained risk. Nine stars, zero dependents, a single maintainer, and a patch-only release history mean real-world edge-case coverage is thin. Mitigate with an exact version pin, the TabsField adapter, a named owner, and a documented decision path (adopt / fork / replicate internally / defer) agreed before the pilot. Roll out in waves — pilot resources first, broader adoption only after one clean Nova upgrade cycle.

Keep it inside Nova resource classes. The package is presentational only: no persistence, no domain logic, no schema. Do not let Tabs/Tab leak into actions, service classes, or shared domain code. Rollback should be deleting the config and the dependency — there are no migrations or data changes to unwind.

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core