Selection Field

Command

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

stable
command cmdk autocomplete search select

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

PropTypeDefaultDescription
type'command'-Field type (required)
labelstring-Label text
optionsOption[]-Array of { label, value, icon? }
placeholderstring'Enter a value...'Trigger text when no value is selected
searchPlaceholderstring'Search...'Placeholder in the search input
emptyTextstring'No option found.'Text shown when search has no matches
helperTextstring-Help text below field
valuestring-Default selected value
columnsPartial<Record<Breakpoint, number>>-Grid columns by breakpoint
disabledboolean | function | ConditionGroupfalseDisable the field
hiddenboolean | function | ConditionGroup | ResponsivefalseHide 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