Quick Drawer
A read-only quick view that slides in beside a list — sticky identity header with status pills and queue position, previous/next stepping, tabbed subjects with counts and attention markers, and a footer that offers the full record.
Preview
Order #1042
3 of 12ORD-2026-1042
Els Bakker, Webshop, placed 8 minutes ago
Money
- Subtotal
- €118.50
- Shipping
- €10.00
- Total
- €128.50
- Outstanding
- €128.50
Facts
- Placed
- 15 Sep 2026, 09:12
- Channel
- Webshop
- Payment reference
- tr_7GkQ2p
Lines will appear here.
Payments will appear here.
This panel shows and never writes. Actions that move money or stock live on the record.
<div class="w-full">
<x-ui.admin.quick-drawer />
</div>
Installation
php artisan ui:add admin-quick-drawer
Note
This component ships an Alpine behavior module at
resources/js/ui/admin-quick-drawer.js. Import it once from your bundle so it registers on alpine:init:
import './admin-quick-drawer.js';
Registry contract
php artisan ui:add admin-quick-drawer
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/quick-drawer.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: Quick Drawer (`admin-quick-drawer`)
A read-only quick view that slides in beside a list — sticky identity header with status pills and queue position, previous/next stepping, tabbed subjects with counts and attention markers, and a footer that offers the full record.
Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.
## Install
```bash
php artisan ui:add admin-quick-drawer
```
## Usage
```blade
<div class="w-full">
<x-ui.admin.quick-drawer />
</div>
```
## Props
- `title` (string, default `Order #1042`) — The subject's title.
- `code` (string|null, default `null`) — A copyable identifier under the title.
- `subtitle` (string|null, default `null`) — One line of context beside the pills.
- `pills` (array, default `[]`) — [['label','tone']] status pills.
- `position` (array|null, default `null`) — ['index','total'] — where this subject sits in the list.
- `tabs` (array, default `[]`) — The record-tabs `tabs` array; panels arrive as named slots keyed by tab.
- `previousHref` (string|null, default `null`) — Previous subject; null disables.
- `nextHref` (string|null, default `null`) — Next subject; null disables.
- `detailHref` (string|null, default `#`) — Open-record link.
- `detailLabel` (string|null, default `null`) — Open-record label.
- `editHref` (string|null, default `null`) — Edit link; hidden when null.
- `editLabel` (string|null, default `null`) — Edit label.
- `note` (string|null, default `null`) — The footer's honesty line.
- `triggerLabel` (string|null, default `null`) — Label of the sample trigger button.
- `size` ('sm'|'md'|'lg'|'xl', default `lg`) — Sheet width.
## Use when
- Use to structure hierarchy, spacing, and responsiveness so content is easier to scan and navigate.
- Working a queue from a table: answer the questions a row cannot, without leaving the list.
- The record page is heavy and most visits only need the sheet.
## Avoid when
- Do not let layout primitives substitute for semantics, headings, or interaction rules users still need.
- The panel must write (change status, move money) — those actions belong on the record.
- Editing in a side panel — use `sheet` with a form directly.
## Anti-patterns
- Using visual layout as a substitute for semantic structure
## Rules
- Use the `<brok:admin-quick-drawer>` tag (or `<x-ui.admin-quick-drawer>`) 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-quick-drawer
- Registry JSON (files, props, contract): https://brokui.dev/r/open/admin-quick-drawer.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 |
|---|---|---|---|
| title | string | Order #1042 | The subject's title. |
| code | string | null | null | A copyable identifier under the title. |
| subtitle | string | null | null | One line of context beside the pills. |
| pills | array | [] | [['label','tone']] status pills. |
| position | array | null | null | ['index','total'] — where this subject sits in the list. |
| tabs | array | [] | The record-tabs `tabs` array; panels arrive as named slots keyed by tab. |
| previousHref | string | null | null | Previous subject; null disables. |
| nextHref | string | null | null | Next subject; null disables. |
| detailHref | string | null | # | Open-record link. |
| detailLabel | string | null | null | Open-record label. |
| editHref | string | null | null | Edit link; hidden when null. |
| editLabel | string | null | null | Edit label. |
| note | string | null | null | The footer's honesty line. |
| triggerLabel | string | null | null | Label of the sample trigger button. |
| size | 'sm' | 'md' | 'lg' | 'xl' | lg | Sheet width. |
Slots
trigger— Replaces the sample trigger; anything calling `show()` in the sheet scope.<tab key>— One slot per tab key, e.g. `overview`, holding that panel.
Data slots
Stable hooks for CSS overrides and browser tests.
Behavior
- Composed from `sheet` (focus trap, Escape, scroll lock, focus return) and `record-tabs` with URL sync off.
- The step pair sits together and the close button apart, the same on every drawer.
- The panel never carries write actions; the footer offers the record and an optional edit link.
- 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
Structure content hierarchy and responsive relationships.
Use when
- Use to structure hierarchy, spacing, and responsiveness so content is easier to scan and navigate.
- Working a queue from a table: answer the questions a row cannot, without leaving the list.
- The record page is heavy and most visits only need the sheet.
Avoid when
- Do not let layout primitives substitute for semantics, headings, or interaction rules users still need.
- The panel must write (change status, move money) — those actions belong on the record.
- Editing in a side panel — use `sheet` with a form directly.
Use instead
- Semantic HTML with standard flow
Anti-patterns
- Using visual layout as a substitute for semantic structure
- Anatomy
- Theming hooks
Accessibility
- Keyboard
- managed
- Focus
none
- The sheet is a modal dialog labelled by the title; the queue position is text, not only a number in a corner.
- 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
Add a stable wire:key when Livewire can reorder this interactive component.
<div wire:key="admin-quick-drawer-{{ $record->id }}">
<div class="w-full">
<x-ui.admin.quick-drawer />
</div>
</div>
Source
The exact, editable file ui:add writes
into your app. Previews render this same code; there are no preview-only components.
{{--
Quick Drawer — the answers a table row cannot give, without leaving the
list.
A queue is triaged from one panel: the header stays (identity, status,
where you are in the queue), a tab strip names the subjects, and the
footer says once what the panel will not do and offers the full record.
The record page is opened only when the whole history is needed.
Three decisions are the same on every drawer so the panels are one
control to learn: the step pair (previous/next) sits together and the
close button stands apart from it; "3 of 12" is shown beside the title
because it is the difference between working a queue and clicking around
one; and the panel shows and never writes — every action that moves
money or stock needs an idempotent server action and belongs on the
record, so the footer offers the record and an edit link, nothing more.
Composed from the sheet primitive (focus trap, Escape, scroll lock,
polite subject announcement on step) and the record-tabs primitive. Panels
are named slots keyed by tab, exactly as on record-tabs.
Generalised from the Noord-C admin's `quick-drawer.blade.php`, copied
into twenty-four modules with only the subject vocabulary changed.
--}}
@props([
'title' => 'Order #1042',
'code' => null,
'subtitle' => null,
'pills' => [],
'position' => null,
'tabs' => [],
'previousHref' => null,
'nextHref' => null,
'detailHref' => '#',
'detailLabel' => null,
'editHref' => null,
'editLabel' => null,
'note' => null,
'triggerLabel' => null,
'size' => 'lg',
])
@php
// Shapes documented in item.json knowledge.props:
// pills: [['label', 'tone' => 'positive'|'attention'|'negative'|'info'|'neutral']]
// position: ['index' => 3, 'total' => 12] — where this subject sits in the list being worked.
// tabs: the record-tabs `tabs` array; panels arrive as named slots keyed by tab.
$code ??= 'ORD-2026-1042';
$subtitle ??= __('Els Bakker, Webshop, placed 8 minutes ago');
$pills = $pills !== [] ? $pills : [
['label' => __('Awaiting payment'), 'tone' => 'attention'],
['label' => __('Stock reserved'), 'tone' => 'positive'],
];
$position ??= ['index' => 3, 'total' => 12];
$tabs = $tabs !== [] ? $tabs : [
['key' => 'overview', 'label' => __('Overview')],
['key' => 'lines', 'label' => __('Lines'), 'count' => 4],
['key' => 'payments', 'label' => __('Payments'), 'count' => 1, 'attention' => true],
];
$tabs = array_values(array_filter((array) $tabs, 'is_array'));
$detailLabel ??= __('Open record');
$editLabel ??= __('Edit');
$note ??= __('This panel shows and never writes. Actions that move money or stock live on the record.');
$triggerLabel ??= __('Quick view');
$hasPosition = is_array($position) && isset($position['index'], $position['total']);
@endphp
<x-ui.sheet data-surface="admin" {{ $attributes->merge(['class' => 'contents']) }}>
@isset($trigger)
{{ $trigger }}
@else
<x-ui.button variant="outline" size="sm" x-on:click="show()" aria-haspopup="dialog" x-bind:aria-expanded="open" x-ref="trigger">
{{ $triggerLabel }}
</x-ui.button>
@endisset
<x-ui.sheet.content side="end" :size="$size" class="flex flex-col !p-0">
<div data-slot="quick-drawer" class="flex min-h-0 flex-1 flex-col">
<x-ui.sheet.header class="shrink-0 gap-2 border-b border-border p-6">
<div class="flex items-start justify-between gap-4">
<div class="min-w-0">
<div class="flex min-w-0 flex-wrap items-center gap-2">
<x-ui.sheet.title class="line-clamp-2 text-base leading-6">{{ $title }}</x-ui.sheet.title>
@if ($hasPosition)
<span data-slot="quick-drawer-position" class="shrink-0 text-xs text-muted-foreground tabular-nums">
{{ __(':index of :total', ['index' => $position['index'], 'total' => $position['total']]) }}
</span>
@endif
</div>
@if ($code !== null)
<p data-slot="quick-drawer-code" class="mt-2 font-mono text-xs text-muted-foreground">{{ $code }}</p>
@endif
<div class="mt-2 flex min-w-0 flex-wrap items-center gap-2">
@foreach ($pills as $pill)
<x-ui.badge :tone="$pill['tone'] ?? 'neutral'" size="status">{{ $pill['label'] ?? '' }}</x-ui.badge>
@endforeach
@if ($subtitle !== null)
<x-ui.sheet.description class="min-w-0 truncate text-xs">{{ $subtitle }}</x-ui.sheet.description>
@endif
</div>
</div>
<div class="flex shrink-0 items-center gap-2">
<nav class="flex items-center gap-0.5 rounded-md border border-border p-0.5" aria-label="{{ __('Step through the list') }}">
<x-ui.button variant="ghost" size="icon-sm" :href="$previousHref" :disabled="$previousHref === null" :icon-label="__('Previous')" rel="prev">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><path d="m18 15-6-6-6 6"/></svg>
</x-ui.button>
<x-ui.button variant="ghost" size="icon-sm" :href="$nextHref" :disabled="$nextHref === null" :icon-label="__('Next')" rel="next">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
</x-ui.button>
</nav>
<x-ui.button variant="ghost" size="icon-sm" class="ms-2" x-on:click="hide()" :icon-label="__('Close')" title="Esc">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg>
</x-ui.button>
</div>
</div>
</x-ui.sheet.header>
<div data-slot="quick-drawer-body" class="min-h-0 flex-1 overflow-y-auto p-6">
<x-ui.admin.record-tabs :tabs="$tabs" :param="false">
@foreach ($tabs as $tab)
@php
$key = $tab['key'];
$panel = isset($$key) && $$key instanceof \Illuminate\View\ComponentSlot ? $$key : null;
@endphp
@if ($panel !== null)
<x-slot :name="$key">{{ $panel }}</x-slot>
@elseif ($loop->first)
<x-slot :name="$key">
<div class="flex flex-col gap-4">
<x-ui.admin.record-section :title="__('Money')" :hint="false" more-href="#">
<x-ui.fact-list :facts="[
['label' => __('Subtotal'), 'value' => '€118.50'],
['label' => __('Shipping'), 'value' => '€10.00'],
['label' => __('Total'), 'value' => '€128.50'],
['label' => __('Outstanding'), 'value' => '€128.50', 'tone' => 'attention'],
]" />
</x-ui.admin.record-section>
<x-ui.admin.record-section :title="__('Facts')" :hint="false">
<x-ui.fact-list :facts="[
['label' => __('Placed'), 'value' => '15 Sep 2026, 09:12'],
['label' => __('Channel'), 'value' => 'Webshop'],
['label' => __('Payment reference'), 'value' => 'tr_7GkQ2p', 'mono' => true],
]" />
</x-ui.admin.record-section>
</div>
</x-slot>
@endif
@endforeach
</x-ui.admin.record-tabs>
</div>
<x-ui.sheet.footer class="mt-0 shrink-0 flex-row flex-wrap items-center justify-between gap-4 border-t border-border bg-surface-2 px-6 py-4">
<p data-slot="quick-drawer-note" class="min-w-0 flex-1 basis-64 text-xs leading-4 text-pretty text-muted-foreground">{{ $note }}</p>
<div class="flex shrink-0 items-center gap-2">
@if ($detailHref !== null)
<x-ui.button variant="outline" size="sm" :href="$detailHref">{{ $detailLabel }}</x-ui.button>
@endif
@if ($editHref !== null)
<x-ui.button size="sm" :href="$editHref">{{ $editLabel }}</x-ui.button>
@endif
</div>
</x-ui.sheet.footer>
</div>
</x-ui.sheet.content>
</x-ui.sheet>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.0.2 - 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