Skip to content
Brok UI

Loading…

No results

List Toolbar

Open source

One GET form of the controls that apply to a whole list — search, a date-window select with an inline custom range, saved views, a reset link when modified, and an export/tools cluster.

Version
v1.0.2
Stability
stable
License
MIT
Related
Saved Views
Record Search
Active Filters
List Footer
Admin Table

Preview

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

Installation

terminal
php artisan ui:add admin-list-toolbar

Note

This component ships an Alpine behavior module at resources/js/ui/admin-list-toolbar.js. Import it once from your bundle so it registers on alpine:init:

resources/js/ui/index.js JS
import './admin-list-toolbar.js';

Registry contract

php artisan ui:add admin-list-toolbar 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/list-toolbar.blade.php
Registry dependencies
input select button saved-views date-range-picker
Packages
composer: jml/brok:^0.2
npm: alpinejs

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-list-toolbar.md
# Brok UI: List Toolbar (`admin-list-toolbar`)

One GET form of the controls that apply to a whole list — search, a date-window select with an inline custom range, saved views, a reset link when modified, and an export/tools cluster.

Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.

## Install

```bash
php artisan ui:add admin-list-toolbar
```

## Usage

```blade
<div class="w-full">
    <x-ui.admin.list-toolbar />
</div>
```

## Props

- `action` (string|null, default `null`) — GET target; empty submits to the current URL.
- `searchValue` (string, default ``) — Current search text.
- `searchName` (string, default `q`) — Search input name.
- `searchPlaceholder` (string|null, default `null`) — Search placeholder.
- `window` (string|null, default `null`) — Active window key; defaults to the second preset.
- `windowName` (string, default `window`) — Window select name; the custom range posts as `<windowName>_start`/`_end`.
- `windows` (array|null, default `null`) — [['key','label']] presets; `custom` is appended.
- `windowFrom` (string, default ``) — Custom range start.
- `windowTo` (string, default ``) — Custom range end.
- `views` (array, default `[]`) — The saved-views `views` array.
- `viewsMode` ('tabs'|'menu', default `tabs`) — Saved-views mode.
- `modified` (bool, default `false`) — Whether the list differs from its default view; shows the reset link.
- `resetHref` (string|null, default `null`) — Reset link target; defaults to `?`.
- `exportHref` (string|null, default `null`) — Export link; `#` sample when omitted.
- `exportLabel` (string|null, default `null`) — Export label.
- `hidden` (array, default `[]`) — [['name','value']] hidden inputs so a resubmit keeps filter state.

## Use when

- Use when the option set is long enough that search, suggestion, or filtering speeds selection.
- The row above an admin table: views choose which rows, this row refines how, column headers own field filters.
- Search and the date window must survive as URL state (a shareable, back-button-safe list).

## Avoid when

- Do not use advanced selection UI when a simpler visible control would be clearer.
- Faceted per-field filters — use `filters` beside it.
- A client-only list that never round-trips — bind your own controls to the list state.

## Anti-patterns

- Using advanced search for a tiny list

## Rules

- Use the `<brok:admin-list-toolbar>` tag (or `<x-ui.admin-list-toolbar>`) 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-list-toolbar
- Registry JSON (files, props, contract): https://brokui.dev/r/open/admin-list-toolbar.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
action string | null null GET target; empty submits to the current URL.
searchValue string Current search text.
searchName string q Search input name.
searchPlaceholder string | null null Search placeholder.
window string | null null Active window key; defaults to the second preset.
windowName string window Window select name; the custom range posts as `<windowName>_start`/`_end`.
windows array | null null [['key','label']] presets; `custom` is appended.
windowFrom string Custom range start.
windowTo string Custom range end.
views array [] The saved-views `views` array.
viewsMode 'tabs' | 'menu' tabs Saved-views mode.
modified bool false Whether the list differs from its default view; shows the reset link.
resetHref string | null null Reset link target; defaults to `?`.
exportHref string | null null Export link; `#` sample when omitted.
exportLabel string | null null Export label.
hidden array [] [['name','value']] hidden inputs so a resubmit keeps filter state.

Slots

  • search — Replaces the search input (for example `record-search`).
  • tools — Replaces the export button.

Data slots

Stable hooks for CSS overrides and browser tests.

list-toolbar

Behavior

  • Choosing a preset window submits the form; choosing Custom reveals a start/end pair and an Apply button instead.
  • The reset link renders only when `modified` is true.
  • Installs a JavaScript behavior module when the registry item includes resources/js/ui files.
  • Declares registry capability flags: a11y, interactive, responsive, rtl, darkMode, localized, alpine.

Guidance

Searchable selection and filtering

Find and select within a long or uncertain option set.

Use when

  • Use when the option set is long enough that search, suggestion, or filtering speeds selection.
  • The row above an admin table: views choose which rows, this row refines how, column headers own field filters.
  • Search and the date window must survive as URL state (a shareable, back-button-safe list).

Avoid when

  • Do not use advanced selection UI when a simpler visible control would be clearer.
  • Faceted per-field filters — use `filters` beside it.
  • A client-only list that never round-trips — bind your own controls to the list state.

Use instead

  • Visible choices for small sets
  • Dedicated filter panel for complex refinement

Anti-patterns

  • Using advanced search for a tiny list
Anatomy
list-toolbar
Theming hooks
list-toolbar

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
Keyboard
native
Focus
native
  • Every control has a label (visually hidden where the placeholder carries the meaning); the form works without JavaScript through the `<noscript>` submit.
  • 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-list-toolbar-{{ $record->id }}">
    <div class="w-full">
        <x-ui.admin.list-toolbar />
    </div>
</div>

Validation

Validation support: native. Keep the error message connected with aria-describedby.

livewire-form.blade.php Blade
<form wire:submit="save" class="space-y-2">
    <brok:admin-list-toolbar
        wire:model="value"
        :aria-invalid="$errors->has('value') ? 'true' : 'false'"
        aria-describedby="value-error"
    />

    @error('value')
        <p id="value-error" role="alert">{{ $message }}</p>
    @enderror

    <brok:button type="submit" wire:loading.attr="disabled">
        <span wire:loading.remove>Save</span>
        <span wire:loading>Saving…</span>
    </brok:button>
</form>

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/list-toolbar.blade.php Blade
{{--
    List Toolbar — the controls that apply to the whole list: search, the
    date window, which saved view you are in, a way back to the default,
    and the page-level tools (export) on the end side.

    The split is deliberate and the same on every list: views choose WHICH
    rows you are looking at, the toolbar refines HOW, and field filters live
    on the column headers where the data is. This row never grows a filter
    per column; that is what the applied-filters readout and the table's
    own header menus are for.

    The date window is a select of presets plus "Custom…", which reveals a
    start/end pair inline instead of in a popover: the whole row is one GET
    form, so a teleported panel would fall outside it. The reset link is
    rendered only when the list is `modified`, because a control that does
    nothing teaches the operator to stop reading the row.

    Generalised from the Noord-C admin's `toolbar.blade.php`, copied into
    twenty-five modules; belongs in `<x-slot:toolbar>` of an admin table.
--}}
@props([
    'action' => null,
    'searchValue' => '',
    'searchName' => 'q',
    'searchPlaceholder' => null,
    'window' => null,
    'windowName' => 'window',
    'windows' => null,
    'windowFrom' => '',
    'windowTo' => '',
    'views' => [],
    'viewsMode' => 'tabs',
    'modified' => false,
    'resetHref' => null,
    'exportHref' => null,
    'exportLabel' => null,
    'hidden' => [],
])

@php
    // Shapes documented in item.json knowledge.props:
    //   windows: [['key' => '30d', 'label' => 'Last 30 days']] — presets; a `custom` entry is appended automatically.
    //   views: the saved-views `views` array (href XOR event per view).
    //   hidden: [['name' => 'status', 'value' => 'paid']] — filter state to carry through a resubmit so search never drops a filter.
    $searchPlaceholder ??= __('Search by number, name, or email…');
    $windows ??= [
        ['key' => '7d', 'label' => __('Last 7 days')],
        ['key' => '30d', 'label' => __('Last 30 days')],
        ['key' => '90d', 'label' => __('Last 90 days')],
        ['key' => 'ytd', 'label' => __('This year')],
        ['key' => 'all', 'label' => __('All time')],
    ];
    $window ??= $windows[1]['key'] ?? 'all';
    $windows = collect($windows)->filter(fn ($entry) => is_array($entry))->push(['key' => 'custom', 'label' => __('Custom…')])->values();

    $views = $views !== [] ? $views : [
        ['key' => 'recent', 'label' => __('Recent activity'), 'href' => '?view=recent', 'active' => true, 'system' => true],
        ['key' => 'attention', 'label' => __('Needs attention'), 'href' => '?view=attention', 'count' => 9, 'tone' => 'attention', 'system' => true],
        ['key' => 'at_risk', 'label' => __('At risk'), 'href' => '?view=at_risk', 'count' => 4, 'system' => true],
    ];

    $exportLabel ??= __('Export');
    $exportHref ??= '#';
    $resetHref ??= '?';
@endphp

<form
    method="GET"
    action="{{ $action ?? '' }}"
    data-slot="list-toolbar"
    data-surface="admin"
    x-data="{ window: @js($window) }"
    {{ $attributes->merge(['class' => 'flex min-w-0 flex-wrap items-center gap-2']) }}
>
    @foreach ($hidden as $field)
        <input type="hidden" name="{{ $field['name'] ?? '' }}" value="{{ $field['value'] ?? '' }}" />
    @endforeach

    @isset($search)
        {{ $search }}
    @else
        <label class="min-w-0 flex-1 basis-64 sm:max-w-xs">
            <span class="sr-only">{{ __('Search') }}</span>
            <x-ui.input type="search" :name="$searchName" :value="$searchValue" :placeholder="$searchPlaceholder" autocomplete="off" enterkeyhint="search" />
        </label>
    @endisset

    <div class="flex min-w-0 flex-wrap items-center gap-2">
        <label class="min-w-0">
            <span class="sr-only">{{ __('Date window') }}</span>
            <x-ui.select :name="$windowName" x-model="window" x-on:change="if (window !== 'custom') $el.form.requestSubmit()">
                @foreach ($windows as $option)
                    <option value="{{ $option['key'] }}" @selected($option['key'] === $window)>{{ $option['label'] }}</option>
                @endforeach
            </x-ui.select>
        </label>

        <div x-show="window === 'custom'" x-cloak class="flex min-w-0 flex-wrap items-center gap-2">
            <div class="min-w-0 basis-64">
                <x-ui.date-range-picker :name="$windowName" :start-value="$windowFrom" :end-value="$windowTo" />
            </div>
            <x-ui.button type="submit" variant="secondary" size="sm">{{ __('Apply') }}</x-ui.button>
        </div>
    </div>

    @if ($views !== [])
        <x-ui.saved-views :views="$views" :mode="$viewsMode" />
    @endif

    <div class="ms-auto flex min-w-0 flex-wrap items-center gap-2">
        @if ($modified)
            <x-ui.button variant="link" size="sm" :href="$resetHref">{{ __('Reset view') }}</x-ui.button>
        @endif

        @isset($tools)
            {{ $tools }}
        @elseif ($exportHref !== null)
            <x-ui.button variant="outline" size="sm" :href="$exportHref">{{ $exportLabel }}</x-ui.button>
        @endisset
    </div>

    <noscript><x-ui.button type="submit" variant="secondary" size="sm">{{ __('Apply') }}</x-ui.button></noscript>
</form>

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