DocsComponentsCommand
Command
A searchable list of commands, with keyboard navigation.
Alpine.js8 files
<pb-ui.command class="w-full max-w-md rounded-lg border shadow-md">
<pb-ui.command-input placeholder="Type a command or search…" />
<pb-ui.command-list>
<pb-ui.command-empty>No results found.</pb-ui.command-empty>
<pb-ui.command-group heading="Suggestions">
<pb-ui.command-item>Calendar</pb-ui.command-item>
<pb-ui.command-item>Search emoji</pb-ui.command-item>
<pb-ui.command-item>Calculator</pb-ui.command-item>
</pb-ui.command-group>
<pb-ui.command-separator />
<pb-ui.command-group heading="Settings">
<pb-ui.command-item>Profile <pb-ui.command-shortcut>⌘P</pb-ui.command-shortcut></pb-ui.command-item>
<pb-ui.command-item>Billing <pb-ui.command-shortcut>⌘B</pb-ui.command-shortcut></pb-ui.command-item>
<pb-ui.command-item>Settings <pb-ui.command-shortcut>⌘S</pb-ui.command-shortcut></pb-ui.command-item>
</pb-ui.command-group>
</pb-ui.command-list>
</pb-ui.command>Installation
pyblade-ui add command- Install the dependencies
Alpine.js, loaded in your
<head>. - Copy these files into your projectcomponents/ui/command.html
<div data-slot="command" x-data="{ search: '', match(text) { return text.toLowerCase().includes(this.search.trim().toLowerCase()) }, all() { return [...this.$refs.list.querySelectorAll('[data-slot=command-item]')] }, found() { return this.all().filter(i => this.match(i.textContent)) }, items() { return this.all().filter(i => i.offsetParent !== null) }, move(step) { const items = this.items(), at = items.indexOf(document.activeElement); const next = items[at + step] || items[step > 0 ? 0 : items.length - 1]; if (next) next.focus(); }, }" x-on:keydown.down.prevent="move(1)" x-on:keydown.up.prevent="move(-1)" {{ attributes.cn("flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground") }}>{{ slot }}</div>components/ui/command_input.html<div data-slot="command-input-wrapper" class="flex h-9 items-center gap-2 border-b px-3"> <svg class="size-4 shrink-0 opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21 21-4.34-4.34"/><circle cx="11" cy="11" r="8"/></svg> <input type="text" autocomplete="off" x-model="search" x-on:keydown.enter.prevent="found()[0] && found()[0].click()" data-slot="command-input" {{ attributes.cn("flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50") }} /> </div>components/ui/command_list.html<div role="listbox" data-slot="command-list" x-ref="list" {{ attributes.cn("max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto") }}>{{ slot }}</div>components/ui/command_empty.html<div data-slot="command-empty" x-show="search && !found().length" x-cloak {{ attributes.cn("py-6 text-center text-sm") }}>{{ slot }}</div>components/ui/command_group.html@props({'heading': ''}) <div role="group" data-slot="command-group" x-show="[...$el.querySelectorAll('[data-slot=command-item]')].some(i => match(i.textContent))" {{ attributes.cn("overflow-hidden p-1 text-foreground") }}> <div data-slot="command-group-heading" class="px-2 py-1.5 text-xs font-medium text-muted-foreground">{{ heading }}</div> {{ slot }} </div>components/ui/command_item.html@props({'href': None}) <a role="option" data-slot="command-item" @if(href)href="{{ href }}"@endif tabindex="0" x-show="match($el.textContent)" {{ attributes.cn("relative flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground") }}>{{ slot }}</a>components/ui/command_separator.html<div role="separator" data-slot="command-separator" x-show="!search" {{ attributes.cn("-mx-1 h-px bg-border") }}></div>components/ui/command_shortcut.html<span data-slot="command-shortcut" {{ attributes.cn("ml-auto text-xs tracking-widest text-muted-foreground") }}>{{ slot }}</span> - Update the path to fit your project
The
componentsfolder is the one set inpyblade.toml.
Usage
The tags <pb-ui.command> <pb-ui.command-input> <pb-ui.command-list> <pb-ui.command-empty> <pb-ui.command-group> <pb-ui.command-item> <pb-ui.command-separator> <pb-ui.command-shortcut> are available in all your templates.
<pb-ui.command class="rounded-lg border shadow-md">
<pb-ui.command-input placeholder="Type a command or search..." />
<pb-ui.command-list>
<pb-ui.command-empty>No results found.</pb-ui.command-empty>
<pb-ui.command-group heading="Suggestions">
<pb-ui.command-item href="/calendar">Calendar</pb-ui.command-item>
<pb-ui.command-item>Search emoji</pb-ui.command-item>
</pb-ui.command-group>
</pb-ui.command-list>
</pb-ui.command>Examples
Combobox (with Popover)
<pb-ui.popover align="start">
<pb-slot:trigger><pb-ui.button variant="outline" class="w-52 justify-between">Select a framework…</pb-ui.button></pb-slot:trigger>
<pb-ui.command class="-m-4 w-72">
<pb-ui.command-input placeholder="Search…" />
<pb-ui.command-list>
<pb-ui.command-empty>No framework found.</pb-ui.command-empty>
<pb-ui.command-group heading="Frameworks">
<pb-ui.command-item>Django</pb-ui.command-item>
<pb-ui.command-item>Flask</pb-ui.command-item>
<pb-ui.command-item>FastAPI</pb-ui.command-item>
<pb-ui.command-item>Litestar</pb-ui.command-item>
</pb-ui.command-group>
</pb-ui.command-list>
</pb-ui.command>
</pb-ui.popover>Props
<pb-ui.command-group>
| Prop | Default |
|---|---|
| heading | '' |
<pb-ui.command-item>
| Prop | Default |
|---|---|
| href | None |
Any other attribute (class, id, x-on:click, hx-post…) is passed to the root element; your Tailwind classes win over the component's.