Docs Components Navigation

Command

React example follows below.
ts
import * as React from "react";
/**
* Command palette fixture. `groups` is the shared catalogue every adapter
* renders; `query` and `activeIndex` pin the filtered state the
* five-framework render proof compares (typing "de" drops the Navigate group
* entirely and leaves two Actions rows, of which the second is active).
*
* Every item carries an explicit `value`: React's search-text extractor is
* `value ?? (typeof label === "string" ? label : "")` while the other four
* use ui-core's `defaultCommandSearchText` (`value ?? label`), so pinning
* `value` makes the filter identical by construction rather than by
* coincidence.
*/
const commandFixture = {
id: "command",
trigger: "Open command palette",
placeholder: "Search commands…",
query: "de",
activeIndex: 1,
groups: [
{
heading: "Navigate",
items: [
{ label: "Go to dashboard", value: "dashboard", shortcut: "G D" },
{ label: "Go to settings", value: "settings", shortcut: "G S" },
],
},
{
heading: "Actions",
items: [
{ label: "Deploy ledger-api", value: "deploy", shortcut: "⌘ ⏎" },
{ label: "Redeploy last build", value: "redeploy" },
{ label: "Roll back release", value: "rollback", shortcut: "⌘ R" },
],
},
],
} satisfies {
id: string;
trigger: string;
placeholder: string;
query: string;
activeIndex: number;
groups: readonly {
heading: string;
items: readonly { label: string; value: string; shortcut?: string }[];
}[];
};
import { Button, Command, type CommandGroup } from "@grassroot/ui-react";
/**
* Command is a full-viewport overlay, so — like DialogExample — the canonical
* example is the closed palette behind its trigger: mounting it already open
* would blanket the catalogue page. The open/filtered state is pinned by the
* fixture (`query`, `activeIndex`) and proved in each adapter's own test.
*/
export default function CommandDemo() {
const [open, setOpen] = React.useState(false);
const groups: CommandGroup[] = commandFixture.groups.map((group) => ({
heading: group.heading,
items: group.items.map((item) => ({
label: item.label,
value: item.value,
shortcut: item.shortcut,
})),
}));
return (
<div>
<Button onClick={() => setOpen(true)}>{commandFixture.trigger}</Button>
<Command
groups={groups}
open={open}
onOpenChange={setOpen}
placeholder={commandFixture.placeholder}
/>
</div>
);
}
ts
<script setup lang="ts">
import { ref } from "vue";
/**
* Command palette fixture. `groups` is the shared catalogue every adapter
* renders; `query` and `activeIndex` pin the filtered state the
* five-framework render proof compares (typing "de" drops the Navigate group
* entirely and leaves two Actions rows, of which the second is active).
*
* Every item carries an explicit `value`: React's search-text extractor is
* `value ?? (typeof label === "string" ? label : "")` while the other four
* use ui-core's `defaultCommandSearchText` (`value ?? label`), so pinning
* `value` makes the filter identical by construction rather than by
* coincidence.
*/
const commandFixture = {
id: "command",
trigger: "Open command palette",
placeholder: "Search commands…",
query: "de",
activeIndex: 1,
groups: [
{
heading: "Navigate",
items: [
{ label: "Go to dashboard", value: "dashboard", shortcut: "G D" },
{ label: "Go to settings", value: "settings", shortcut: "G S" },
],
},
{
heading: "Actions",
items: [
{ label: "Deploy ledger-api", value: "deploy", shortcut: "⌘ ⏎" },
{ label: "Redeploy last build", value: "redeploy" },
{ label: "Roll back release", value: "rollback", shortcut: "⌘ R" },
],
},
],
} satisfies {
id: string;
trigger: string;
placeholder: string;
query: string;
activeIndex: number;
groups: readonly {
heading: string;
items: readonly { label: string; value: string; shortcut?: string }[];
}[];
};
import { Button, Command } from "@grassroot/ui-vue";
const open = ref(false);
</script>
<template>
<div>
<Button @click="open = true">{{ commandFixture.trigger }}</Button>
<Command
v-model:open="open"
:groups="commandFixture.groups"
:placeholder="commandFixture.placeholder"
/>
</div>
</template>
ts
import { Component, signal } from "@angular/core";
/**
* Command palette fixture. `groups` is the shared catalogue every adapter
* renders; `query` and `activeIndex` pin the filtered state the
* five-framework render proof compares (typing "de" drops the Navigate group
* entirely and leaves two Actions rows, of which the second is active).
*
* Every item carries an explicit `value`: React's search-text extractor is
* `value ?? (typeof label === "string" ? label : "")` while the other four
* use ui-core's `defaultCommandSearchText` (`value ?? label`), so pinning
* `value` makes the filter identical by construction rather than by
* coincidence.
*/
const commandFixture = {
id: "command",
trigger: "Open command palette",
placeholder: "Search commands…",
query: "de",
activeIndex: 1,
groups: [
{
heading: "Navigate",
items: [
{ label: "Go to dashboard", value: "dashboard", shortcut: "G D" },
{ label: "Go to settings", value: "settings", shortcut: "G S" },
],
},
{
heading: "Actions",
items: [
{ label: "Deploy ledger-api", value: "deploy", shortcut: "⌘ ⏎" },
{ label: "Redeploy last build", value: "redeploy" },
{ label: "Roll back release", value: "rollback", shortcut: "⌘ R" },
],
},
],
} satisfies {
id: string;
trigger: string;
placeholder: string;
query: string;
activeIndex: number;
groups: readonly {
heading: string;
items: readonly { label: string; value: string; shortcut?: string }[];
}[];
};
import { GrassrootButton, GrassrootCommand } from "@grassroot/ui-angular";
@Component({
selector: "app-command-demo",
standalone: true,
imports: [GrassrootButton, GrassrootCommand],
template: `
<div>
<grassroot-button (click)="open.set(true)">{{ fixture.trigger }}</grassroot-button>
<grassroot-command
[groups]="fixture.groups"
[(open)]="open"
[placeholder]="fixture.placeholder"
/>
</div>
`,
})
export class CommandDemoComponent {
protected readonly fixture = commandFixture;
protected readonly open = signal(false);
}
export default CommandDemoComponent;
ts
<script lang="ts">
import { Button, Command } from "@grassroot/ui-svelte";
/**
* Command palette fixture. `groups` is the shared catalogue every adapter
* renders; `query` and `activeIndex` pin the filtered state the
* five-framework render proof compares (typing "de" drops the Navigate group
* entirely and leaves two Actions rows, of which the second is active).
*
* Every item carries an explicit `value`: React's search-text extractor is
* `value ?? (typeof label === "string" ? label : "")` while the other four
* use ui-core's `defaultCommandSearchText` (`value ?? label`), so pinning
* `value` makes the filter identical by construction rather than by
* coincidence.
*/
const commandFixture = {
id: "command",
trigger: "Open command palette",
placeholder: "Search commands…",
query: "de",
activeIndex: 1,
groups: [
{
heading: "Navigate",
items: [
{ label: "Go to dashboard", value: "dashboard", shortcut: "G D" },
{ label: "Go to settings", value: "settings", shortcut: "G S" },
],
},
{
heading: "Actions",
items: [
{ label: "Deploy ledger-api", value: "deploy", shortcut: "⌘ ⏎" },
{ label: "Redeploy last build", value: "redeploy" },
{ label: "Roll back release", value: "rollback", shortcut: "⌘ R" },
],
},
],
} satisfies {
id: string;
trigger: string;
placeholder: string;
query: string;
activeIndex: number;
groups: readonly {
heading: string;
items: readonly { label: string; value: string; shortcut?: string }[];
}[];
};
let open = $state(false);
</script>
<div>
<Button onClick={() => (open = true)}>{commandFixture.trigger}</Button>
<Command
groups={commandFixture.groups}
{open}
onOpenChange={(next) => (open = next)}
placeholder={commandFixture.placeholder}
/>
</div>
ts
/** @jsxImportSource solid-js */
import { createSignal } from "solid-js";
import { Button, Command } from "@grassroot/ui-solid";
/**
* Command palette fixture. `groups` is the shared catalogue every adapter
* renders; `query` and `activeIndex` pin the filtered state the
* five-framework render proof compares (typing "de" drops the Navigate group
* entirely and leaves two Actions rows, of which the second is active).
*
* Every item carries an explicit `value`: React's search-text extractor is
* `value ?? (typeof label === "string" ? label : "")` while the other four
* use ui-core's `defaultCommandSearchText` (`value ?? label`), so pinning
* `value` makes the filter identical by construction rather than by
* coincidence.
*/
const commandFixture = {
id: "command",
trigger: "Open command palette",
placeholder: "Search commands…",
query: "de",
activeIndex: 1,
groups: [
{
heading: "Navigate",
items: [
{ label: "Go to dashboard", value: "dashboard", shortcut: "G D" },
{ label: "Go to settings", value: "settings", shortcut: "G S" },
],
},
{
heading: "Actions",
items: [
{ label: "Deploy ledger-api", value: "deploy", shortcut: "⌘ ⏎" },
{ label: "Redeploy last build", value: "redeploy" },
{ label: "Roll back release", value: "rollback", shortcut: "⌘ R" },
],
},
],
} satisfies {
id: string;
trigger: string;
placeholder: string;
query: string;
activeIndex: number;
groups: readonly {
heading: string;
items: readonly { label: string; value: string; shortcut?: string }[];
}[];
};
export default function CommandDemo() {
const [open, setOpen] = createSignal(false);
return (
<div>
<Button onClick={() => setOpen(true)}>{commandFixture.trigger}</Button>
<Command
groups={commandFixture.groups.map((group) => ({
heading: group.heading,
items: [...group.items],
}))}
open={open()}
onOpenChange={setOpen}
placeholder={commandFixture.placeholder}
/>
</div>
);
}

Installation

API Reference

Import

import * as React from "react";
import { Button, Command, type CommandGroup } from "@grassroot/ui-react";

Props

Prop
Type
Default
Requirement
Description
groups
CommandGroup[]
required
value
string | undefined
optional
defaultValue
string | undefined
optional
onValueChange
((value: string) => void) | undefined
optional
query
string | undefined
optional
defaultQuery
string | undefined
optional
onQueryChange
((query: string) => void) | undefined
optional
open
boolean | undefined
optional
defaultOpen
boolean | undefined
optional
onOpenChange
((open: boolean) => void) | undefined
optional
filter
((item: CommandItem, normalizedQuery: string) => boolean) | undefined
optional
dir
"ltr" | "rtl" | undefined
optional
placeholder
string | undefined
optional
hotkey
boolean | undefined
true
optional
Bind ⌘K / Ctrl-K to toggle open.
Prop
groups
Type
CommandGroup[]
Default
Requirement
required
Description
Prop
value
Type
string | undefined
Default
Requirement
optional
Description
Prop
defaultValue
Type
string | undefined
Default
Requirement
optional
Description
Prop
onValueChange
Type
((value: string) => void) | undefined
Default
Requirement
optional
Description
Prop
query
Type
string | undefined
Default
Requirement
optional
Description
Prop
defaultQuery
Type
string | undefined
Default
Requirement
optional
Description
Prop
onQueryChange
Type
((query: string) => void) | undefined
Default
Requirement
optional
Description
Prop
open
Type
boolean | undefined
Default
Requirement
optional
Description
Prop
defaultOpen
Type
boolean | undefined
Default
Requirement
optional
Description
Prop
onOpenChange
Type
((open: boolean) => void) | undefined
Default
Requirement
optional
Description
Prop
filter
Type
((item: CommandItem, normalizedQuery: string) => boolean) | undefined
Default
Requirement
optional
Description
Prop
dir
Type
"ltr" | "rtl" | undefined
Default
Requirement
optional
Description
Prop
placeholder
Type
string | undefined
Default
Requirement
optional
Description
Prop
hotkey
Type
boolean | undefined
Default
true
Requirement
optional
Description
Bind ⌘K / Ctrl-K to toggle open.

Events

EventPayload
onValueChange((value: string) => void) | undefined
onQueryChange((query: string) => void) | undefined
onOpenChange((open: boolean) => void) | undefined

Import

import { ref } from "vue";
import { Button, Command } from "@grassroot/ui-vue";

Props

Prop
Type
Default
Requirement
groups
readonly PublicCommandGroup[]
required
value
string
undefined
optional
defaultValue
string
undefined
optional
query
string
undefined
optional
defaultQuery
string
undefined
optional
open
boolean | undefined
undefined
optional
defaultOpen
boolean
false
optional
filter
(item: VueCommandItem["original"], query: string) => boolean
undefined
optional
dir
"ltr" | "rtl"
undefined
optional
placeholder
string
"Type a command…"
optional
hotkey
boolean
true
optional
Prop
groups
Type
readonly PublicCommandGroup[]
Default
Requirement
required
Prop
value
Type
string
Default
undefined
Requirement
optional
Prop
defaultValue
Type
string
Default
undefined
Requirement
optional
Prop
query
Type
string
Default
undefined
Requirement
optional
Prop
defaultQuery
Type
string
Default
undefined
Requirement
optional
Prop
open
Type
boolean | undefined
Default
undefined
Requirement
optional
Prop
defaultOpen
Type
boolean
Default
false
Requirement
optional
Prop
filter
Type
(item: VueCommandItem["original"], query: string) => boolean
Default
undefined
Requirement
optional
Prop
dir
Type
"ltr" | "rtl"
Default
undefined
Requirement
optional
Prop
placeholder
Type
string
Default
"Type a command…"
Requirement
optional
Prop
hotkey
Type
boolean
Default
true
Requirement
optional

Events

EventPayload
update:open(_open: boolean) => true
update:value(_value: string) => true
update:query(_query: string) => true

Import

import { Component, signal } from "@angular/core";
import { GrassrootButton, GrassrootCommand } from "@grassroot/ui-angular";

Props

Prop
Type
Default
Requirement
groups
readonly CoreCommandGroup<AngularCommandItem>[]
required
value
string
optional
defaultValue
string
optional
open
boolean
optional
defaultOpen
boolean
false
optional
query
string
optional
defaultQuery
string
optional
filter
CommandFilter<AngularCommandItem>
optional
dir
"ltr" | "rtl"
optional
placeholder
string
"Type a command…"
optional
hotkey
boolean
true
optional
Prop
groups
Type
readonly CoreCommandGroup<AngularCommandItem>[]
Default
Requirement
required
Prop
value
Type
string
Default
Requirement
optional
Prop
defaultValue
Type
string
Default
Requirement
optional
Prop
open
Type
boolean
Default
Requirement
optional
Prop
defaultOpen
Type
boolean
Default
false
Requirement
optional
Prop
query
Type
string
Default
Requirement
optional
Prop
defaultQuery
Type
string
Default
Requirement
optional
Prop
filter
Type
CommandFilter<AngularCommandItem>
Default
Requirement
optional
Prop
dir
Type
"ltr" | "rtl"
Default
Requirement
optional
Prop
placeholder
Type
string
Default
"Type a command…"
Requirement
optional
Prop
hotkey
Type
boolean
Default
true
Requirement
optional

Events

EventPayload
valueChangestring
openChangeboolean
queryChangestring

Import

import { Button, Command } from "@grassroot/ui-svelte";

Props

Prop
Type
Default
Requirement
groups
readonly PublicCommandGroup[]
required
open
boolean
optional
defaultOpen
boolean
false
optional
value
string
optional
defaultValue
string
optional
query
string
optional
defaultQuery
string
optional
filter
CommandMachineInput["filter"]
optional
dir
"ltr" | "rtl"
optional
placeholder
string
"Type a command…"
optional
hotkey
boolean
true
optional
onOpenChange
(open: boolean) => void
optional
onValueChange
(value: string) => void
optional
onQueryChange
(query: string) => void
optional
Prop
groups
Type
readonly PublicCommandGroup[]
Default
Requirement
required
Prop
open
Type
boolean
Default
Requirement
optional
Prop
defaultOpen
Type
boolean
Default
false
Requirement
optional
Prop
value
Type
string
Default
Requirement
optional
Prop
defaultValue
Type
string
Default
Requirement
optional
Prop
query
Type
string
Default
Requirement
optional
Prop
defaultQuery
Type
string
Default
Requirement
optional
Prop
filter
Type
CommandMachineInput["filter"]
Default
Requirement
optional
Prop
dir
Type
"ltr" | "rtl"
Default
Requirement
optional
Prop
placeholder
Type
string
Default
"Type a command…"
Requirement
optional
Prop
hotkey
Type
boolean
Default
true
Requirement
optional
Prop
onOpenChange
Type
(open: boolean) => void
Default
Requirement
optional
Prop
onValueChange
Type
(value: string) => void
Default
Requirement
optional
Prop
onQueryChange
Type
(query: string) => void
Default
Requirement
optional

Events

EventPayload
onOpenChange(open: boolean) => void
onValueChange(value: string) => void
onQueryChange(query: string) => void

Import

import { createSignal } from "solid-js";
import { Button, Command } from "@grassroot/ui-solid";

Props

Prop
Type
Default
Requirement
groups
CommandGroup[]
required
value
string
optional
defaultValue
string
optional
onValueChange
(value: string) => void
optional
query
string
optional
defaultQuery
string
optional
onQueryChange
(query: string) => void
optional
open
boolean
optional
defaultOpen
boolean
optional
onOpenChange
(open: boolean) => void
optional
filter
(item: CommandItem, normalizedQuery: string) => boolean
optional
dir
"ltr" | "rtl"
optional
placeholder
string
"Type a command…"
optional
hotkey
boolean
true
optional
Prop
groups
Type
CommandGroup[]
Default
Requirement
required
Prop
value
Type
string
Default
Requirement
optional
Prop
defaultValue
Type
string
Default
Requirement
optional
Prop
onValueChange
Type
(value: string) => void
Default
Requirement
optional
Prop
query
Type
string
Default
Requirement
optional
Prop
defaultQuery
Type
string
Default
Requirement
optional
Prop
onQueryChange
Type
(query: string) => void
Default
Requirement
optional
Prop
open
Type
boolean
Default
Requirement
optional
Prop
defaultOpen
Type
boolean
Default
Requirement
optional
Prop
onOpenChange
Type
(open: boolean) => void
Default
Requirement
optional
Prop
filter
Type
(item: CommandItem, normalizedQuery: string) => boolean
Default
Requirement
optional
Prop
dir
Type
"ltr" | "rtl"
Default
Requirement
optional
Prop
placeholder
Type
string
Default
"Type a command…"
Requirement
optional
Prop
hotkey
Type
boolean
Default
true
Requirement
optional

Events

EventPayload
onValueChange(value: string) => void
onQueryChange(query: string) => void
onOpenChange(open: boolean) => void