S
Selection Field

Select

A dropdown select field for choosing from a list of options. Uses Radix UI for accessibility.

stable
select dropdown options choice

Interactive demo of select field

Where are you located?

What best describes you?

/**
 * Select Field Demo - Interactive examples of select dropdowns
 */

import { Form, FormBuilder } from '@saastro/forms';

import { FormProvider } from '@/components/FormProvider';
import { TooltipProvider } from '@/components/ui/tooltip';

const config = FormBuilder.create('select-demo')
  .layout('manual')
  .columns(12)
  .addField('country', (f) =>
    f
      .type('select')
      .label('Country')
      .placeholder('Select a country')
      .helperText('Where are you located?')
      .options([
        { label: 'United States', value: 'us' },
        { label: 'Canada', value: 'ca' },
        { label: 'Mexico', value: 'mx' },
        { label: 'United Kingdom', value: 'uk' },
        { label: 'Germany', value: 'de' },
        { label: 'France', value: 'fr' },
        { label: 'Spain', value: 'es' },
        { label: 'Japan', value: 'jp' },
      ])
      .required('Please select a country')
      .columns({ default: 12, md: 6 }),
  )
  .addField('role', (f) =>
    f
      .type('select')
      .label('Role')
      .placeholder('Select your role')
      .helperText('What best describes you?')
      .options([
        { label: 'Developer', value: 'developer' },
        { label: 'Designer', value: 'designer' },
        { label: 'Product Manager', value: 'pm' },
        { label: 'Marketing', value: 'marketing' },
        { label: 'Other', value: 'other' },
      ])
      .required('Please select a role')
      .columns({ default: 12, md: 6 }),
  )
  .addField('experience', (f) =>
    f
      .type('select')
      .label('Experience Level')
      .placeholder('Select experience')
      .options([
        { label: 'Junior (0-2 years)', value: 'junior' },
        { label: 'Mid-level (2-5 years)', value: 'mid' },
        { label: 'Senior (5-10 years)', value: 'senior' },
        { label: 'Lead (10+ years)', value: 'lead' },
      ])
      .required()
      .columns({ default: 12 }),
  )
  .addStep('main', ['country', 'role', 'experience'])
  .build();

export default function SelectDemo() {
  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 select field provides a dropdown menu for selecting a single option from a list. It’s built on Radix UI Select for full accessibility support.

For native HTML select elements (better mobile support), see Native Select.

Usage

Basic Select

import { FormBuilder } from '@saastro/forms';

const config = FormBuilder.create('profile')
  .addField('country', (f) =>
    f
      .type('select')
      .label('Country')
      .placeholder('Select a country')
      .options([
        { label: 'United States', value: 'us' },
        { label: 'Canada', value: 'ca' },
        { label: 'Mexico', value: 'mx' },
        { label: 'United Kingdom', value: 'uk' },
      ])
      .required('Please select a country'),
  )
  .addStep('main', ['country'])
  .build();

With Icons

import { Mail, Phone, MessageSquare } from 'lucide-react';

.addField('contactMethod', (f) =>
  f.type('select')
    .label('Preferred Contact')
    .options([
      { label: 'Email', value: 'email', icon: <Mail className="w-4 h-4" /> },
      { label: 'Phone', value: 'phone', icon: <Phone className="w-4 h-4" /> },
      { label: 'SMS', value: 'sms', icon: <MessageSquare className="w-4 h-4" /> },
    ])
    .required()
)

JSON Configuration

{
  "type": "select",
  "label": "Country",
  "placeholder": "Select a country",
  "options": [
    { "label": "United States", "value": "us" },
    { "label": "Canada", "value": "ca" },
    { "label": "Mexico", "value": "mx" }
  ],
  "schema": {
    "required": true,
    "requiredMessage": "Please select a country"
  }
}

Props

PropTypeDefaultDescription
type'select'-Field type (required)
optionsOption[]-Array of options (required)
placeholderstring-Placeholder text when no option selected
labelstring-Field label
helperTextstring-Help text shown below field
size'sm' | 'md' | 'lg''md'Input size variant
columnsPartial<Record<Breakpoint, number>>-Grid columns by breakpoint
disabledboolean | function | ConditionGroupfalseDisable the select
hiddenboolean | function | ConditionGroup | ResponsivefalseHide the field

Option Type

type Option = {
  label: string;
  value: string;
  icon?: ReactNode;
  iconProps?: Record<string, unknown>;
};

Conditional Options

Options can be loaded dynamically or filtered based on other field values:

// In your component
const countryOptions = useMemo(() => {
  if (region === 'north-america') {
    return [
      { label: 'United States', value: 'us' },
      { label: 'Canada', value: 'ca' },
      { label: 'Mexico', value: 'mx' },
    ];
  }
  // ... other regions
}, [region]);

Validation

Required

.addField('country', (f) =>
  f.type('select')
    .label('Country')
    .options([...])
    .required('Please select a country')
)

With Zod

import { z } from 'zod';

.addField('country', (f) =>
  f.type('select')
    .label('Country')
    .options([...])
    .schema(z.enum(['us', 'ca', 'mx', 'uk']))
)

Styling

Custom Classes

Apply CSS classes to different parts of the field:

.addField('country', (f) =>
  f.type('select')
    .label('Country')
    .options([...])
    .classNames({
      wrapper: 'bg-muted/50 p-4 rounded-lg',
      input: 'border-primary',
      label: 'text-primary font-medium',
      error: 'text-destructive',
      helper: 'text-muted-foreground text-xs',
    })
)

Responsive Layout

.addField('country', (f) =>
  f.type('select')
    .label('Country')
    .options([...])
    .columns({ default: 12, md: 6, lg: 4 })
    .size('lg')
)

JSON Styling

{
  "type": "select",
  "label": "Country",
  "wrapper_className": "bg-muted/50 p-4 rounded-lg",
  "input_className": "border-primary",
  "label_className": "text-primary font-medium",
  "columns": { "default": 12, "md": 6 }
}
  • Native Select - HTML native select element
  • Combobox - Searchable select with autocomplete
  • Command - Command palette style selection
  • Radio - Radio buttons for visible options

Accessibility

  • Full keyboard navigation
  • Screen reader announcements
  • Focus management
  • ARIA attributes automatically applied
  • Works with form labels