Interactive demo of select field
/**
* 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
| Prop | Type | Default | Description |
|---|---|---|---|
type | 'select' | - | Field type (required) |
options | Option[] | - | Array of options (required) |
placeholder | string | - | Placeholder text when no option selected |
label | string | - | Field label |
helperText | string | - | Help text shown below field |
size | 'sm' | 'md' | 'lg' | 'md' | Input size variant |
columns | Partial<Record<Breakpoint, number>> | - | Grid columns by breakpoint |
disabled | boolean | function | ConditionGroup | false | Disable the select |
hidden | boolean | function | ConditionGroup | Responsive | false | Hide 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 }
}
Related Fields
- 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