Record Tabs
A line tab strip for a record — one tab per subject with a count and an attention marker, deep-linkable through `?tab=`, panels supplied as named slots keyed by tab.
Preview
Overview will appear here.
Lines will appear here.
Payments will appear here.
Shipments will appear here.
History will appear here.
<div class="w-full">
<x-ui.admin.record-tabs />
</div>
Installation
php artisan ui:add admin-record-tabs
Note
This component ships an Alpine behavior module at
resources/js/ui/admin-record-tabs.js. Import it once from your bundle so it registers on alpine:init:
import './admin-record-tabs.js';
Registry contract
php artisan ui:add admin-record-tabs
writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.
-
resources/views/components/ui/admin/record-tabs.blade.php
Use with AI
A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.
# Brok UI: Record Tabs (`admin-record-tabs`)
A line tab strip for a record — one tab per subject with a count and an attention marker, deep-linkable through `?tab=`, panels supplied as named slots keyed by tab.
Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.
## Install
```bash
php artisan ui:add admin-record-tabs
```
## Usage
```blade
<div class="w-full">
<x-ui.admin.record-tabs />
</div>
```
## Props
- `tabs` (array, default `[]`) — [['key','label','count' => null,'attention' => false,'disabled' => false]].
- `default` (string|null, default `null`) — Initially active key; defaults to the first.
- `param` (string|false, default `tab`) — Query key for URL sync; `false` turns it off (inside a drawer).
- `label` (string|null, default `null`) — Accessible name of the tab list.
## Use when
- Use to orient users and help them move across pages, sections, or commands.
- A record has several subjects (overview, lines, payments, history) a person opens it for.
- A colleague must be sent straight to one subject by link.
## Avoid when
- Do not hide primary wayfinding in novelty interactions or deep nested structures if straightforward navigation would be clearer.
- Two or three static content panels on a marketing page — use `tabs` directly.
- Navigation between pages — use links.
## Anti-patterns
- Hiding primary wayfinding in novelty interactions
## Rules
- Use the `<brok:admin-record-tabs>` tag (or `<x-ui.admin-record-tabs>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.
## Links
- Docs: https://brokui.dev/docs/components/admin-record-tabs
- Registry JSON (files, props, contract): https://brokui.dev/r/open/admin-record-tabs.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| tabs | array | [] | [['key','label','count' => null,'attention' => false,'disabled' => false]]. |
| default | string | null | null | Initially active key; defaults to the first. |
| param | string | false | tab | Query key for URL sync; `false` turns it off (inside a drawer). |
| label | string | null | null | Accessible name of the tab list. |
Slots
<tab key>— One slot per tab key holding that panel; a placeholder renders for a tab without one.
Data slots
Stable hooks for CSS overrides and browser tests.
Behavior
- The tab named in the URL wins over `default` on load; selections are written back with replaceState.
- The attention marker is a character plus a screen-reader label, never colour alone.
- Installs a JavaScript behavior module when the registry item includes resources/js/ui files.
- Declares registry capability flags: a11y, interactive, responsive, rtl, darkMode, localized.
Guidance
Orient users and move between destinations.
Use when
- Use to orient users and help them move across pages, sections, or commands.
- A record has several subjects (overview, lines, payments, history) a person opens it for.
- A colleague must be sent straight to one subject by link.
Avoid when
- Do not hide primary wayfinding in novelty interactions or deep nested structures if straightforward navigation would be clearer.
- Two or three static content panels on a marketing page — use `tabs` directly.
- Navigation between pages — use links.
Use instead
- Visible links and local navigation
Anti-patterns
- Hiding primary wayfinding in novelty interactions
- Anatomy
- Theming hooks
Accessibility
- Keyboard
- No component-owned keyboard interaction; native element behavior applies.
- Focus
none
- Roving tabindex, arrow keys, Home/End and swipe come from the tabs primitive; counts are text inside the tab name.
- Semantic HTML and a stable
data-slotattribute for styling and scripting hooks. - Focus-visible rings use the
ringtoken, so keyboard focus is always visible. - Disabled and invalid states are conveyed to assistive tech, not by color alone.
- Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
- Labels go through
__()and layout uses logical properties (ms-*,text-start), so it mirrors underdir="rtl"— flip the preview to RTL to confirm. - Dark mode uses the same semantic tokens under the
darkclass; high contrast follows forced-color system tokens.
Livewire
Livewire can update this component through forwarded wire:* attributes.
Source
The exact, editable file ui:add writes
into your app. Previews render this same code; there are no preview-only components.
{{--
Record Tabs — one tab per subject a person opens a record for, each
carrying the count of what it holds and a marker when that subject needs
a person.
The Overview tab comes first: a short sheet answers most visits without
a second click. The marker is a character, not only a colour, so it
survives a colour-deficient reader and a monochrome screen, and it is
named for a screen reader. Tabs are deep-linkable (`?tab=…`) through the
tabs primitive's `param`, so a colleague can be sent straight to the
History of a record.
Panels are named slots keyed by the tab: `<x-slot:overview>`,
`<x-slot:history>`. A tab without a slot renders a placeholder panel so
the strip stays honest while a screen is still being built.
Generalised from the Noord-C admin's `profile/tabs.blade.php` and the
nineteen per-module tab strips that repeated it.
--}}
@props([
'tabs' => [],
'default' => null,
// Query key the active tab is read from and written to; `false` turns
// deep-linking off (a tab strip inside a drawer must not touch the URL).
'param' => 'tab',
'label' => null,
])
@php
// Shapes documented in item.json knowledge.props:
// tabs: [['key', 'label', 'count' => null, 'attention' => false, 'disabled' => false]]
$tabs = $tabs !== [] ? $tabs : [
['key' => 'overview', 'label' => __('Overview')],
['key' => 'lines', 'label' => __('Lines'), 'count' => 4],
['key' => 'payments', 'label' => __('Payments'), 'count' => 2, 'attention' => true],
['key' => 'shipments', 'label' => __('Shipments'), 'count' => 1],
['key' => 'history', 'label' => __('History'), 'count' => 12],
];
$tabs = array_values(array_filter((array) $tabs, 'is_array'));
$default ??= $tabs[0]['key'] ?? null;
$label ??= __('Record sections');
@endphp
<x-ui.tabs
variant="line"
:default="$default"
:param="$param ?: null"
data-surface="admin"
{{ $attributes->merge(['class' => 'min-w-0 gap-6']) }}
>
<div data-slot="record-tabs" class="min-w-0 overflow-x-auto">
<x-ui.tabs.list :aria-label="$label" class="w-full min-w-max">
@foreach ($tabs as $tab)
<x-ui.tabs.trigger :value="$tab['key']" :disabled="$tab['disabled'] ?? false" :data-attention="($tab['attention'] ?? false) ? 'true' : null">
<span>{{ $tab['label'] ?? $tab['key'] }}</span>
@if (($tab['count'] ?? null) !== null)
<x-ui.badge variant="soft-neutral" size="sm" class="tabular-nums">{{ $tab['count'] }}</x-ui.badge>
@endif
@if ($tab['attention'] ?? false)
<span class="font-bold text-destructive-text" aria-hidden="true">!</span>
<span class="sr-only">{{ __('needs attention') }}</span>
@endif
</x-ui.tabs.trigger>
@endforeach
</x-ui.tabs.list>
</div>
@foreach ($tabs as $tab)
@php
$key = $tab['key'];
// A named slot arrives as a variable of the same name; only a real
// slot counts, never a prop or a loop variable that happens to share
// the key.
$panel = isset($$key) && $$key instanceof \Illuminate\View\ComponentSlot ? $$key : null;
@endphp
<x-ui.tabs.content :value="$key" data-record-tab="{{ $key }}">
@if ($panel !== null)
{{ $panel }}
@else
<p class="text-sm text-muted-foreground">
{{ __(':label will appear here.', ['label' => $tab['label'] ?? $key]) }}
</p>
@endif
</x-ui.tabs.content>
@endforeach
</x-ui.tabs>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.0.1 - Status
- Stable
- License
-
open - Accessibility reviewed
- No review date recorded
- Last breaking change
- No date recorded
- Deprecation
- Not deprecated
- Contract
-
v6 - Foundation
-
≥ 1.0.0