dkulyk/nova-tabs
A Laravel Nova package that organizes resource fields into clean, customizable tabs on create, update, and detail pages. It improves form usability, supports flexible tab configuration, and keeps complex forms easy to navigate.
Install (in a Nova project):
composer require dkulyk/nova-tabs
No service provider registration is required — it is a Nova field package, auto-discovered by Laravel. There is no publishable config or asset step in the typical install.
Where to look first:
vendor/dkulyk/nova-tabs/src/ — the two classes you actually use, the Tabs container and the Tab child field.README.md for the exact make() signature of the release pinned in your composer.lock (this package is archived, so the README is the contract you have).fields() method — that is the only integration surface.First use case — breaking a crowded form into tabs:
use Dkulyk\NovaTabs\Tabs;
use Dkulyk\NovaTabs\Tab;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\Textarea;
public function fields(Request $request)
{
return [
Tabs::make([
Tab::make('Overview', [
Text::make('Name')->rules('required'),
Textarea::make('Summary'),
]),
Tab::make('Settings', [
Text::make('Slug'),
Boolean::make('Published'),
]),
]),
];
}
Start by moving only the fields you already have into a single Tabs::make() and confirming it renders on the detail and update screens before adding a second tab. That isolates package issues from field issues.
Split create/update by resource method. Because fields() returns a plain array, you can extract tab construction into a private method and reuse it across resources:
protected function contentTabs(Request $request): array
{
return [
Tabs::make([
Tab::make('Content', [/* ... */]),
Tab::make('SEO', [/* ... */]),
]),
];
}
Call it from fields() in both the parent and any related resources that need the same grouping.
Mix tabs with non-tab fields. Keep status/meta fields outside the tab container so they stay visible on every tab:
return [
Status::make('Status'),
Tabs::make([ /* ... */ ])->withMeta(['class' => 'mt-4']),
$this->auditFields(),
];
Combine with panels. Nest Panel inside a Tab when a single tab is long, and keep the Tabs container at the top level of fields(). Avoid nesting Tabs inside Tabs.
Gate whole tabs. Apply the same visibility API you use on fields (->canSee(...) / ->hideWhenCreating()), evaluated on the Tab instance. Compute the closure from $request->user() so tabs disappear for restricted roles rather than rendering empty.
Conditional fields inside a tab. Field-level dependsOn/resolveUsing continues to work normally — the tab is only a layout wrapper, so dependent fields still respond to the fields they reference even when those fields live in another tab.
Validation is unaffected. Rules declared on fields inside a tab are collected into the standard Nova validation payload. Use ->rules() on the field, not on the Tab, and keep rule ordering (required before max) as you would in a flat form.
Detail vs. form. Fields marked onlyOnDetail() or onlyOnForms() remain respected inside tabs; use this to show computed values on detail while keeping the update form lean.
composer.lock and budget for a fork if you are on a recent Nova.sortable/showOnIndex, confirm the container renders sensibly on the index — layout wrappers were not designed for index tables. Force child fields ->hideFromIndex() and surface key columns as separate top-level fields if the table looks broken.Text::make('Name') cleanly; Nova resolves by field name. Keep field names unique across all tabs in a resource.Tab::make([...]); reordering the top-level array reorders tabs but not child fields inside a tab. Drag to reorder one level at a time.dd($this->fields($request)) inside fields() to confirm the tab returns its children. If the container renders but the tab bodies are empty, the issue is almost always a Nova version mismatch in the compiled component, not your field definitions.php artisan nova:publish after reinstalling), then hard-refresh the browser — stale bundles are the most common cause.Tab/Tabs or forking the view/JS. For anything non-trivial, evaluate a maintained alternative (e.g. eminiarts/nova-tabs or outl1ne/nova-tabs) rather than patching an archived package.nova-tabs as a temporary layout shim. Isolate tab construction in dedicated traits (HasTabbedFields) so that swapping the package for a maintained one is a one-file change per resource.How can I help you explore Laravel packages today?