PHPackages                             jibaymcs/tabbed - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. jibaymcs/tabbed

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

jibaymcs/tabbed
===============

A FilamentPHP plugin to manage records in manageable tabs

v1.0.4(1mo ago)81.1k↑160%1MITJavaScriptPHP ^8.2CI passing

Since Apr 9Pushed 1mo agoCompare

[ Source](https://github.com/JibayMcs/Tabbed)[ Packagist](https://packagist.org/packages/jibaymcs/tabbed)[ Docs](https://github.com/jibaymcs/tabbed)[ GitHub Sponsors](https://github.com/JibayMcs)[ RSS](/packages/jibaymcs-tabbed/feed)WikiDiscussions 5.x Synced 2w ago

READMEChangelog (5)Dependencies (16)Versions (6)Used By (0)

Tabbed - In-app tab system for FilamentPHP v5
=============================================

[](#tabbed---in-app-tab-system-for-filamentphp-v5)

[![Latest Version on Packagist](https://camo.githubusercontent.com/a1104001a421be03f114e044d2f21afd6f9f3ef98ce105e3e6cd157c0ac471ff/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6a696261796d63732f7461626265642e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/jibaymcs/tabbed)[![Total Downloads](https://camo.githubusercontent.com/bc6df4fa02c6df755f2ceb0d661b67a758fbe6eb3d7e6343d03e3dfa22921fe1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6a696261796d63732f7461626265642e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/jibaymcs/tabbed)

A FilamentPHP v5 plugin that brings IDE/browser-style tabs to your panel. Open resource pages (Edit, View, Create, List) in tabs, switch between them instantly without losing state, and organize your workflow with drag &amp; drop, renaming, and context menus.

Features
--------

[](#features)

- Open any Filament resource page in a tab
- Instant tab switching (no page reload, state preserved)
- Drag &amp; drop tab reordering
- Inline tab renaming (double-click)
- Right-click context menu (rename, close, close others, close all)
- Middle-click to close tabs (opt-in)
- Configurable tab bar position (topbar, page start, content start, etc.)
- LocalStorage persistence across page navigations
- Background tab opening
- Custom tab labels
- Custom tab colors (accent, background, text) with Filament Color support
- Hover cards on tabs (rich tooltip with custom content on hover)
- Lazy loading &amp; destroy inactive (performance optimization)
- Dropdown mode (compact button replacing the full tab bar)
- Dirty state detection with unsaved changes confirmation modal
- Post-save redirect interception (stay in tab after save, create-to-edit transformation)
- Pinned tabs (anchored left, protected from bulk close, visually distinct)
- Tab search in overflow/dropdown menu (filter by name, keyboard navigation)
- Tab duplication via context menu
- Granular permissions (global + per-tab with `$record` closures)
- Keyboard shortcuts (configurable, no browser conflicts)
- Reopen last closed tab (history stack, context menu + shortcut)
- Dark mode support
- Translations: English, French &amp; Spanish

Installation
------------

[](#installation)

```
composer require jibaymcs/tabbed
```

Add the plugin's views to your custom theme CSS file:

```
@source '../../../../vendor/jibaymcs/tabbed/resources/**/*.blade.php';
```

Setup
-----

[](#setup)

Register the plugin in your `PanelProvider`:

```
use JibayMcs\Tabbed\TabbedPlugin;

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

Usage
-----

[](#usage)

### Option 1: Automatic with trait

[](#option-1-automatic-with-trait)

Add `HasTabbedActions` to your Resource to automatically include the "Open in tab" action on every table row:

```
use JibayMcs\Tabbed\Traits\HasTabbedActions;

class UserResource extends Resource
{
    use HasTabbedActions;

    // Your resource code — no other changes needed
}
```

### Option 2: Manual action

[](#option-2-manual-action)

Add `OpenInTabAction` manually in your table configuration for more control:

```
use JibayMcs\Tabbed\Actions\OpenInTabAction;

public static function table(Table $table): Table
{
    return $table
        ->recordActions([
            OpenInTabAction::make(),
            // ...other actions
        ]);
}
```

### Option 3: Row click

[](#option-3-row-click)

Make clicking a table row open the record in a tab instead of navigating to the edit page:

```
public static function table(Table $table): Table
{
    return $table
        ->recordUrl(null)
        ->recordAction('tabbed')
        ->recordActions([
            OpenInTabAction::make()
                ->hiddenLabel()
                ->background()
                ->tabName(fn ($record) => "Ticket #{$record->id}"),
        ]);
}
```

- `recordUrl(null)` — disables the default link on the row
- `recordAction('tabbed')` — clicking a row triggers the `OpenInTabAction` via Livewire
- `background()` — opens the tab without switching to it
- `tabName()` — custom label for the tab

### Action options

[](#action-options)

```
OpenInTabAction::make()
    ->tabbedPage('view')                              // Target page: edit, view, create, index (default: from config)
    ->background()                                    // Open tab without switching to it
    ->tabName(fn ($record) => $record->name)          // Custom tab label
    ->resource(UserResource::class)                   // Explicit resource (auto-detected by default)
    ->openFor(fn ($record) => $record->author)        // Resolve target record from parent context (see below)
    ->tabColor(Color::Red)                            // Accent color (left border indicator)
    ->tabBackground(Color::Red)                       // Background color
    ->tabTextColor(Color::Red)                        // Text color
    ->confirmOnClose()                                // Ask confirmation before closing if dirty
    ->closeOnSave()                                   // Auto-close the tab after a successful save
```

### Opening a related record (`openFor`)

[](#opening-a-related-record-openfor)

`OpenInTabAction` opens the page of the record bound to its surrounding context — the row's record on a table, the page's record on an infolist Section, etc. That's almost always what you want.

But when the action lives **inside the page of one model** and you want it to open **a related model**, the bound record (the parent) and the target record are different. Without help, the action would call `$parent->getKey()` and try to open the target page with the parent's id — which 404s silently because that id doesn't exist in the target table.

The `openFor()` callback solves this. It receives the Filament-injected parent record and returns the actual record whose page should open. From that point on, every downstream callback (`tabName`, `hoverCardContent`, `tabColor`, …) receives the **resolved** record — so you can write attribute reads naturally without manually walking the relation each time.

**When to use it:** any time the action's target resource is different from the page/row's resource. Typical example — a "View contact" button in the Contact section of a Ticket infolist:

```
use App\Filament\Resources\Contacts\ContactResource;
use App\Models\Ticket;

OpenInTabAction::make()
    ->resource(ContactResource::class)                // The tab opens a Contact…
    ->tabbedPage('view')
    ->openFor(fn (Ticket $record) => $record->contact) // …resolved from the parent Ticket
    ->visible(fn (Ticket $record) => $record->contact !== null) // Hide when no contact linked
    ->tabName(fn ($record) => $record->fullname);     // $record here = the resolved Contact
```

**When NOT to use it:** when the action sits on a row of the target resource's table, or in any context where the bound `$record` is already the model you want to open. The default behaviour is correct — `openFor` adds no value and just adds noise.

**Edge cases:**

- If the relation may be `null` (optional belongsTo), gate the action with `->visible(...)` as shown above. Opening a tab without a record id falls through to mounting the page with `null` and produces another 404.
- If the resolver throws (typo on the relation name, etc.), the action falls back to the parent record rather than crashing the page — you'll see the same 404 you had before, but the surrounding UI keeps working.

### Per-tab permissions

[](#per-tab-permissions)

Control what users can do with individual tabs. Accepts `bool` or a `Closure` receiving `$record` for conditional logic:

```
OpenInTabAction::make()
    ->canReorder(false)                               // Prevent drag & drop for this tab
    ->canRename(fn ($record) => $record->is_editable) // Conditional rename
    ->canPin(fn ($record) => $record->is_important)   // Conditional pin
    ->canDuplicate(true)                              // Allow duplication (default)
    ->canClose(fn ($record) => ! $record->is_locked)  // Prevent closing locked records
```

Per-tab permissions combine with global settings (`allowReorder`, `allowRename`, etc.) on `TabbedPlugin`. The global setting is the master switch: if it's off, the per-tab setting is ignored. If the global is on, the per-tab closure decides.

### Tab colors

[](#tab-colors)

Customize tab appearance per action. Accepts Filament `Color` palettes, hex values, or any CSS color string:

```
use Filament\Support\Colors\Color;

// Filament Color palette (shade picked automatically)
OpenInTabAction::make()
    ->tabColor(Color::Red)                            // border: shade 500
    ->tabBackground(Color::Red)                       // background: shade 50
    ->tabTextColor(Color::Red)                        // text: shade 700

// Specific shade from a palette
OpenInTabAction::make()
    ->tabColor(Color::Blue[600])

// Hex, rgb, rgba
OpenInTabAction::make()
    ->tabColor('#ef4444')
    ->tabBackground('rgba(254, 242, 242, 0.8)')
    ->tabTextColor('#991b1b')
```

### Hover cards

[](#hover-cards)

Display a rich tooltip when hovering over a tab. The content is fully customizable and has access to the `$record`:

```
use JibayMcs\Tabbed\Enums\HoverCardPosition;
use Illuminate\Support\HtmlString;

// Blade view with record data
OpenInTabAction::make()
    ->hoverCardContent(fn ($record) => view('partials.tab-preview', ['record' => $record]))
    ->hoverCardPosition(HoverCardPosition::Bottom)

// Inline HTML
OpenInTabAction::make()
    ->hoverCardContent(fn ($record) => new HtmlString("{$record->name}{$record->email}"))
    ->hoverCardDelay(400)           // Delay before showing (default: 600ms)
    ->hoverCardLeaveDelay(300)      // Delay before hiding (default: 500ms)

// Plain text
OpenInTabAction::make()
    ->hoverCardContent(fn ($record) => "#{$record->id} - {$record->name}")
    ->hoverCardPosition(HoverCardPosition::Top)

// Disable hover card
OpenInTabAction::make()
    ->hoverCard(false)
```

Available positions: `Top`, `TopStart`, `TopEnd`, `Bottom`, `BottomStart`, `BottomEnd`, `Left`, `Right`.

> **Security note:** Hover card content is rendered as raw HTML (`x-html`). If you include user-provided data, make sure to escape it with `e()` or `htmlspecialchars()` to prevent XSS vulnerabilities.

The hover card stays visible when moving the cursor from the tab to the card. It also works on overflow dropdown items.

### JavaScript events

[](#javascript-events)

You can open/close tabs programmatically from anywhere:

```
// Open a tab
window.dispatchEvent(new CustomEvent('tabbed:open', {
    detail: {
        resource: 'App\\Filament\\Resources\\UserResource',
        page: 'edit',
        recordId: 5,
        label: 'Custom label',     // optional
        background: false,         // optional
    }
}));

// Close a tab
window.dispatchEvent(new CustomEvent('tabbed:close', {
    detail: { id: 'tab-uuid' }
}));
```

**Events dispatched by the plugin:**

EventPayloadDescription`tabbed:tab-opened``{ tab }`A tab was opened`tabbed:tab-closed``{ tab }`A tab was closed`tabbed:tab-activated``{ tabId }`A tab was activated`tabbed:tab-deactivated``{ tabId }`Active tab was toggled off`tabbed:all-closed`—All tabs were closedConfiguration
-------------

[](#configuration)

### Plugin options

[](#plugin-options)

Configure via fluent methods in your `PanelProvider`:

```
TabbedPlugin::make()
    ->defaultPage('view')                                   // Default page on open (default: edit)
    ->renderHook(PanelsRenderHook::TOPBAR_LOGO_AFTER)       // Tab bar position (default: TOPBAR_LOGO_AFTER)
    ->persistKey('my_panel_tabs')                            // localStorage key (default: tabbed_tabs)
    ->middleClickToClose()                                  // Close tabs with middle mouse button (default: off)
    ->showTabIcons(false)                                   // Hide resource icons in tabs (default: true)
    ->lazyLoad()                                            // Only load tab content on first activation (default: off)
    ->destroyInactive()                                     // Destroy inactive tab components to save memory (default: off)
    ->confirmClose()                                        // Confirm before closing tabs with unsaved changes (default: off)
    ->interceptRedirects()                                  // Block post-save redirects inside tabs (default: on)
    ->allowReorder(false)                                   // Disable drag & drop reordering (default: on)
    ->allowRename(false)                                    // Disable inline tab renaming (default: on)
    ->allowPin(false)                                       // Disable tab pinning (default: on)
    ->allowDuplicate(false)                                 // Disable tab duplication (default: on)
    ->allowCloseOthers(false)                               // Hide "Close others" from context menu (default: on)
    ->allowCloseAll(false)                                  // Hide "Close all" from context menu (default: on)
    ->keyboardShortcuts()                                   // Enable keyboard shortcuts with defaults (default: off)
```

### Keyboard shortcuts

[](#keyboard-shortcuts)

Enable keyboard shortcuts for power-user navigation. Disabled by default to avoid unexpected behavior.

```
// Enable with default shortcuts
TabbedPlugin::make()->keyboardShortcuts()

// Custom shortcuts
TabbedPlugin::make()->keyboardShortcuts(
    nextTab: 'ctrl+alt+right',   // Next tab (default)
    prevTab: 'ctrl+alt+left',   // Previous tab (default)
    closeTab: 'alt+w',          // Close active tab (default)
    reopenTab: 'alt+shift+t',   // Reopen last closed tab (default)
)

// Disable
TabbedPlugin::make()->keyboardShortcuts(false)
```

**Default shortcuts:**

ActionShortcutNext tab`Ctrl+Alt+Right`Previous tab`Ctrl+Alt+Left`Close active tab`Alt+W`Reopen last closed`Alt+Shift+T`Shortcuts are ignored when an input, textarea, or select is focused. They use `Alt` as the primary modifier to avoid conflicts with browser shortcuts (`Ctrl+Tab`, `Ctrl+W`, etc.).

Closed tabs are stored in a session-only history stack (max 10). You can also reopen them via the right-click context menu ("Reopen closed tab").

### Performance: Lazy loading &amp; destroy inactive

[](#performance-lazy-loading--destroy-inactive)

By default, all open tabs have their Livewire components created immediately. For better performance with many tabs:

```
// Lazy load: components are created only when a tab is activated for the first time.
// Once loaded, they stay in memory (state preserved on switch).
TabbedPlugin::make()->lazyLoad()

// Destroy inactive: only the active tab has a Livewire component in the DOM.
// Switching tabs destroys the previous component and creates the new one.
// Saves memory but loses form state on switch. Implies lazyLoad.
TabbedPlugin::make()->destroyInactive()

// Keep alive: keep the N most recently visited tabs in memory (LRU).
// Tabs beyond this limit are destroyed. Combines fast switching with memory savings.
TabbedPlugin::make()->destroyInactive(keepAlive: 3)
```

A loading spinner appears in the tab panel while the Livewire component loads, and a small loading indicator is shown on the tab itself.

### Dropdown mode

[](#dropdown-mode)

Replace the full tab bar with a compact dropdown button:

```
TabbedPlugin::make()
    ->hasDropdown()                                         // Enables dropdown mode (default icon + badge)
    ->hasDropdown(                                          // Full customization
        icon: 'phosphor-tabs-duotone',                     // Custom icon (default: heroicon-m-squares-2x2)
        label: 'Tabs',                                     // Optional text label
        countBadge: true,                                   // Show tab count badge (default: true)
        color: 'primary',                                   // Filament color name (default: primary)
        outlined: false,                                    // Outlined style (default: false)
    )

// Icon only, no badge, outlined
TabbedPlugin::make()->hasDropdown(countBadge: false, outlined: true)

// Label only, no icon
TabbedPlugin::make()->hasDropdown(icon: null, label: 'My tabs')

// Icon + label
TabbedPlugin::make()->hasDropdown(icon: 'heroicon-m-squares-2x2', label: 'Tabs')
```

Clicking the button opens a dropdown listing all tabs with icons, active indicator, close buttons, and hover cards. All existing features (lazy load, middle-click, persistence) work in dropdown mode.

### Dirty state &amp; close confirmation

[](#dirty-state--close-confirmation)

The plugin detects unsaved changes in tab forms. When a form field is modified, an orange dot appears on the tab. If confirmation is enabled, closing a dirty tab shows a Filament-style modal instead of closing immediately.

```
// Global: all dirty tabs ask confirmation before closing
TabbedPlugin::make()->confirmClose()

// Per-tab: only specific actions ask confirmation
OpenInTabAction::make()->confirmOnClose()

// Both can be combined: global acts as a default, per-tab overrides
TabbedPlugin::make()->confirmClose()
// A tab without ->confirmOnClose() will still ask because of the global setting
```

The dirty state resets automatically after a successful save (`save` or `create` Livewire calls). The confirmation modal also appears for "Close others" and "Close all" context menu actions when dirty tabs are involved.

### Redirect interception

[](#redirect-interception)

By default, saving a form inside a tab stays in the tab instead of following Filament's redirect (which would navigate away from the tab system). This works for both Edit and Create pages:

- **Edit page**: after save, the redirect is blocked and the user stays in the tab
- **Create page**: after creating a record, the tab automatically transforms into an Edit tab for the new record (new tab ID, updated label and record ID)

```
// Disable redirect interception globally (saves redirect normally)
TabbedPlugin::make()->interceptRedirects(false)

// Auto-close a tab after successful save (serial processing workflow)
OpenInTabAction::make()->closeOnSave()
```

Notifications and other Livewire effects are preserved — only the redirect is blocked.

### Pinned tabs

[](#pinned-tabs)

Right-click a tab and select "Pin" to pin it. Pinned tabs are visually distinct (primary background + pin icon) and anchored to the left of the tab bar.

**Pinned tab protections:**

- "Close others" keeps pinned tabs + the target tab
- "Close all" only closes unpinned tabs
- Pinned tabs are never evicted by `destroyInactive` LRU
- Drag &amp; drop is constrained: pinned tabs can only be reordered among themselves

The close button (x) still works on pinned tabs — pinning protects against bulk close, not individual close. Pin state is persisted in localStorage.

### Tab search

[](#tab-search)

When the overflow dropdown (or dropdown mode menu) contains 5 or more tabs, a search field appears at the top. Type to filter tabs by name in real time (case-insensitive, partial match). Use arrow keys to navigate results and Enter to activate the highlighted tab. Escape clears the search, or closes the menu if the search is already empty.

### Tab duplication

[](#tab-duplication)

Right-click a tab and select "Duplicate" to open the same resource/page/record in a new tab. The duplicate opens right after the original with a numbered suffix (e.g. "User #5 (2)"). Colors, hover card, and other settings are copied from the original.

### Config file

[](#config-file)

Publish the config file for project-wide defaults:

```
php artisan vendor:publish --tag="tabbed-config"
```

```
// config/tabbed.php
return [
    'default_page' => 'edit',
    'persist_key' => 'tabbed_tabs',
];
```

Plugin fluent methods take priority over config file values.

### Tab bar position

[](#tab-bar-position)

The tab bar can be placed at any Filament render hook position:

```
use Filament\View\PanelsRenderHook;

// In the topbar (after the logo)
TabbedPlugin::make()->renderHook(PanelsRenderHook::TOPBAR_LOGO_AFTER)

// At the start of the page content
TabbedPlugin::make()->renderHook(PanelsRenderHook::PAGE_START)

// Inside the main content area
TabbedPlugin::make()->renderHook(PanelsRenderHook::CONTENT_START)
```

The tab bar is rendered at the chosen position, while the tab content panels always render inside ``.

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Credits
-------

[](#credits)

- [JibayMcs](https://github.com/JibayMcs)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

47

—

FairBetter than 93% of packages

Maintenance93

Actively maintained with recent releases

Popularity27

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~8 days

Total

6

Last Release

36d ago

Major Versions

v1.0.3 → 5.x-dev2026-05-22

### Community

Maintainers

![](https://www.gravatar.com/avatar/8fb3e7935d578f288802937e00c80e3bfc6a3608d815e401bef011e7d35b3f7e?d=identicon)[JibayMcs](/maintainers/JibayMcs)

---

Top Contributors

[![JibayMcs](https://avatars.githubusercontent.com/u/7621593?v=4)](https://github.com/JibayMcs "JibayMcs (1 commits)")

---

Tags

laravelfilamentfilament-pluginfilamentphpJibayMcstabbed

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/jibaymcs-tabbed/health.svg)

```
[![Health](https://phpackages.com/badges/jibaymcs-tabbed/health.svg)](https://phpackages.com/packages/jibaymcs-tabbed)
```

###  Alternatives

[jibaymcs/filament-tour

Bring the power of DriverJs to your Filament panels and start a tour !

12451.0k](/packages/jibaymcs-filament-tour)[dotswan/filament-map-picker

Easily pick and retrieve geo-coordinates using a map-based interface in your Filament applications.

128173.7k3](/packages/dotswan-filament-map-picker)[rawilk/profile-filament-plugin

Profile &amp; MFA starter kit for filament.

3913.7k](/packages/rawilk-profile-filament-plugin)[wsmallnews/filament-nestedset

Filament nestedset tree builder powered by kalnoy/nestedset with Filament v4 and v5 support

196.5k14](/packages/wsmallnews-filament-nestedset)[marcelweidum/filament-passkeys

Use passkeys in your filamentphp app

6643.3k1](/packages/marcelweidum-filament-passkeys)[guava/filament-modal-relation-managers

Allows you to embed relation managers inside filament modals.

7976.7k5](/packages/guava-filament-modal-relation-managers)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
