Selection Field

Command

Searchable dropdown powered by cmdk. Renders the same popover UI as combobox.

View as Markdown
stable
commandcmdkautocompletesearchselect

Interactive demo of command field

Type to search and filter options

/**
 * 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 placeholder comes from the package’s built-in English defaults. All default UI strings can be overridden — globally with setDefaultMessages or 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, CommandItem
  • Popover, PopoverTrigger, PopoverContent, Button
  • FormField, 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')
)
  • 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" and aria-expanded
  • Full keyboard navigation; type to filter
  • Screen reader announcements and focus management