Skip to content
Brok UI

Loading…

No results

Quick Drawer

Open source

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.

Version
v1.0.2
Stability
stable
License
MIT
Related
Sheet
Record Tabs
Record Section
Fact List
Admin Table

Preview

previews.components.admin-quick-drawer.default.blade.php Blade
<div class="w-full">
    <x-ui.admin.quick-drawer />
</div>

Installation

terminal
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:

resources/js/ui/index.js JS
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.

  • blade resources/views/components/ui/admin/quick-drawer.blade.php
Registry dependencies
sheet badge button admin-record-tabs admin-record-section fact-list
Packages
composer: jml/brok:^0.2

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.

admin-quick-drawer.md
# 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

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
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.

quick-drawer quick-drawer-body quick-drawer-code quick-drawer-note quick-drawer-position

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

Layout and content structure

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
quick-drawer quick-drawer-position quick-drawer-code quick-drawer-body quick-drawer-note
Theming hooks
quick-drawer

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
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-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, 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 under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Needs wire:key

Add a stable wire:key when Livewire can reorder this interactive component.

livewire-component.blade.php Blade
<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.

resources/views/components/ui/admin/quick-drawer.blade.php Blade
{{--
    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