Interactive demo of command field
/**
* Command Field Demo - Interactive examples of command fields
*/
import { Form, FormBuilder } from '@saastro/forms';
import { FormProvider } from '@/components/FormProvider';
import { TooltipProvider } from '@/components/ui/tooltip';
const config = FormBuilder.create('command-demo')
.layout('manual')
.columns(12)
.addField('framework', (f) =>
f
.type('command')
.label('Framework')
.placeholder('Select a framework...')
.searchPlaceholder('Search frameworks...')
.helperText('Type to search and filter options')
.options([
{ label: 'React', value: 'react' },
{ label: 'Vue', value: 'vue' },
{ label: 'Angular', value: 'angular' },
{ label: 'Svelte', value: 'svelte' },
{ label: 'Solid', value: 'solid' },
{ label: 'Qwik', value: 'qwik' },
{ label: 'Preact', value: 'preact' },
{ label: 'Lit', value: 'lit' },
])
.required('Please select a framework')
.columns({ default: 12, md: 6 }),
)
.addField('language', (f) =>
f
.type('command')
.label('Programming Language')
.placeholder('Select a language...')
.searchPlaceholder('Search languages...')
.emptyText('No matching language.')
.options([
{ label: 'TypeScript', value: 'typescript' },
{ label: 'JavaScript', value: 'javascript' },
{ label: 'Python', value: 'python' },
{ label: 'Rust', value: 'rust' },
{ label: 'Go', value: 'go' },
{ label: 'Java', value: 'java' },
{ label: 'C#', value: 'csharp' },
{ label: 'Ruby', value: 'ruby' },
])
.required()
.columns({ default: 12, md: 6 }),
)
.addStep('main', ['framework', 'language'])
.build();
export default function CommandDemo() {
const handleSubmit = (data: Record<string, unknown>) => {
console.log('Form submitted:', data);
alert('Form submitted! Check console for data.');
};
return (
<TooltipProvider>
<FormProvider>
<Form config={config} onSubmit={handleSubmit} className="space-y-4" />
</FormProvider>
</TooltipProvider>
);
} Overview
The command field is a searchable dropdown built on cmdk. It currently renders the exact same popover UI as combobox: a button trigger that opens a popover containing a search input and a filterable option list. Selecting the already-selected option clears the value.
command exists as a distinct type string, but today there is no visual or behavioral difference from combobox — both accept the same props and require the same components. Use whichever type name reads better in your config.
Usage
Basic Command
import { FormBuilder } from '@saastro/forms';
const config = FormBuilder.create('search-form')
.addField('framework', (f) =>
f
.type('command')
.label('Framework')
.placeholder('Select a framework...')
.searchPlaceholder('Search frameworks...')
.options([
{ label: 'React', value: 'react' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte' },
{ label: 'Angular', value: 'angular' },
{ label: 'Solid', value: 'solid' },
])
.required('Please select a framework'),
)
.addStep('main', ['framework'])
.build();
With Icons
Options accept an optional icon rendered before the label:
import { Globe, Zap, Palette } from 'lucide-react';
.addField('framework', (f) =>
f.type('command')
.label('Framework')
.options([
{ label: 'React', value: 'react', icon: <Globe /> },
{ label: 'Vue', value: 'vue', icon: <Zap /> },
{ label: 'Svelte', value: 'svelte', icon: <Palette /> },
])
)
Custom Search and Empty Text
.addField('country', (f) =>
f.type('command')
.label('Country')
.placeholder('Select a country...')
.searchPlaceholder('Type a country name...')
.emptyText('No matching country.')
.options([
{ label: 'Argentina', value: 'ar' },
{ label: 'Spain', value: 'es' },
{ label: 'United States', value: 'us' },
// ...
])
.required()
)
JSON Configuration
{
"type": "command",
"label": "Framework",
"placeholder": "Select a framework...",
"searchPlaceholder": "Search frameworks...",
"emptyText": "No option found.",
"options": [
{ "label": "React", "value": "react" },
{ "label": "Vue", "value": "vue" },
{ "label": "Svelte", "value": "svelte" }
],
"schema": { "required": true }
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
type | 'command' | - | Field type (required) |
label | string | - | Label text |
options | Option[] | - | Array of { label, value, icon? } |
placeholder | string | 'Enter a value...' | Trigger text when no value is selected |
searchPlaceholder | string | 'Search...' | Placeholder in the search input |
emptyText | string | 'No option found.' | Text shown when search has no matches |
helperText | string | - | Help text below field |
value | string | - | Default selected value |
columns | Partial<Record<Breakpoint, number>> | - | Grid columns by breakpoint |
disabled | boolean | function | ConditionGroup | false | Disable the field |
hidden | boolean | function | ConditionGroup | Responsive | false | Hide the field |
The fallback
placeholdercomes from the package’s built-in English defaults. All default UI strings can be overridden — globally withsetDefaultMessagesor per locale; see the i18n guide.
Required Components
Because command renders the same popover UI as combobox, it needs the popover and button components too — not just the cmdk ones. Your component registry must provide:
Command,CommandInput,CommandList,CommandEmpty,CommandGroup,CommandItemPopover,PopoverTrigger,PopoverContent,ButtonFormField,FormControl,Field,FieldDescription,FieldError
If any of these are missing, the field renders a “missing component” fallback instead. Install the UI primitives with shadcn:
npx shadcn@latest add command popover button
See Component System for how to register them.
Validation
.addField('timezone', (f) =>
f.type('command')
.label('Timezone')
.options([...timezones])
.required('Please select a timezone')
)
Related Fields
- Combobox - Renders identically; same props and components
- Select - Standard dropdown without search
- Native Select - Browser native select
Accessibility
- Built on cmdk (Command Menu)
- Trigger button exposes
role="combobox"andaria-expanded - Full keyboard navigation; type to filter
- Screen reader announcements and focus management