Interactive demo of native select field
/**
* Native Select Field Demo - Interactive examples of native select fields
*/
import { Form, FormBuilder } from '@saastro/forms';
import { FormProvider } from '@/components/FormProvider';
import { TooltipProvider } from '@/components/ui/tooltip';
const config = FormBuilder.create('native-select-demo')
.layout('manual')
.columns(12)
.addField('country', (f) =>
f
.type('native-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' },
])
.required('Please select a country')
.columns({ default: 12, md: 6 }),
)
.addField('language', (f) =>
f
.type('native-select')
.label('Language')
.placeholder('Select language')
.options([
{ label: 'English', value: 'en' },
{ label: 'Spanish', value: 'es' },
{ label: 'French', value: 'fr' },
{ label: 'German', value: 'de' },
])
.value('en')
.required()
.columns({ default: 12, md: 6 }),
)
.addField('size', (f) =>
f
.type('native-select')
.label('T-Shirt Size')
.placeholder('Select size')
.options([
{ label: 'Extra Small (XS)', value: 'xs' },
{ label: 'Small (S)', value: 's' },
{ label: 'Medium (M)', value: 'm' },
{ label: 'Large (L)', value: 'l' },
{ label: 'Extra Large (XL)', value: 'xl' },
])
.required()
.columns({ default: 12 }),
)
.addStep('main', ['country', 'language', 'size'])
.build();
export default function NativeSelectDemo() {
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 native select field uses the browser’s built-in <select> element. It’s ideal when you need maximum compatibility, mobile-friendly dropdowns, or when the styled Select is overkill. The first option always renders with an empty value and shows the placeholder text.
Default placeholder. When
placeholderis omitted, the first option falls back to the built-in English default'Enter a value...'. Set aplaceholderper field or override the defaults globally withsetDefaultMessages— see the Internationalization guide.
Usage
Basic Native Select
import { FormBuilder } from '@saastro/forms';
const config = FormBuilder.create('form')
.addField('country', (f) =>
f
.type('native-select')
.label('Country')
.placeholder('Select a country')
.options([
{ label: 'United States', value: 'us' },
{ label: 'Canada', value: 'ca' },
{ label: 'Mexico', value: 'mx' },
])
.required('Please select a country'),
)
.addStep('main', ['country'])
.build();
With Default Value
.addField('language', (f) =>
f.type('native-select')
.label('Language')
.options([
{ label: 'English', value: 'en' },
{ label: 'Spanish', value: 'es' },
{ label: 'French', value: 'fr' },
])
.value('en')
.required()
)
With Many Options
Options render as a flat list — <optgroup> grouping is not supported.
.addField('timezone', (f) =>
f.type('native-select')
.label('Timezone')
.options([
{ label: 'America/New_York', value: 'america-new-york' },
{ label: 'America/Los_Angeles', value: 'america-la' },
{ label: 'Europe/London', value: 'europe-london' },
{ label: 'Europe/Paris', value: 'europe-paris' },
{ label: 'Asia/Tokyo', value: 'asia-tokyo' },
])
.required()
)
JSON Configuration
{
"type": "native-select",
"label": "Country",
"placeholder": "Select a country",
"options": [
{ "label": "United States", "value": "us" },
{ "label": "Canada", "value": "ca" },
{ "label": "Mexico", "value": "mx" }
],
"schema": { "required": true }
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
type | 'native-select' | - | Field type (required) |
label | string | - | Label text |
placeholder | string | - | Placeholder option text |
helperText | string | - | Help text below field |
options | Option[] | - | Array of options (required) |
schema | z.ZodType | ValidationRules | - | Validation: a Zod schema or serializable JSON rules (required — fluent methods like .required() populate it for you) |
value | string | - | Initial selected value |
size | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | - | Declared but not applied by the native-select renderer |
columns | Partial<Record<Breakpoint, number>> | - | Column span (1–12) per breakpoint, in manual layout |
disabled | boolean | function | ConditionGroup | false | Disable the select |
hidden | boolean | function | ConditionGroup | Responsive | false | Hide the field |
Native Select vs Styled Select
| Use Native Select | Use Select |
|---|---|
| Mobile-first apps | Custom styling needed |
| Maximum compatibility | Rich interactions |
| Plain text options | Options with icons |
Validation
Required Selection
.addField('size', (f) =>
f.type('native-select')
.label('Size')
.options([
{ label: 'Small', value: 's' },
{ label: 'Medium', value: 'm' },
{ label: 'Large', value: 'l' },
])
.required('Please select a size')
)
Styling
Custom Classes
.addField('country', (f) =>
f.type('native-select')
.label('Country')
.options([
{ label: 'United States', value: 'us' },
{ label: 'Canada', value: 'ca' },
])
.required()
.classNames({
wrapper: 'bg-muted/30 p-3 rounded-lg',
input: 'border-primary bg-background',
label: 'text-sm font-medium',
error: 'text-destructive text-sm',
})
)
Responsive Layout
Per-field columns apply when the form uses manual layout — see the Layout guide.
.addField('language', (f) =>
f.type('native-select')
.label('Language')
.options([
{ label: 'English', value: 'en' },
{ label: 'Spanish', value: 'es' },
])
.optional()
.columns({ default: 12, md: 6, lg: 4 })
)
JSON Styling
{
"type": "native-select",
"label": "Country",
"options": [
{ "label": "United States", "value": "us" },
{ "label": "Canada", "value": "ca" }
],
"schema": { "required": true },
"wrapper_className": "bg-muted/30 p-3 rounded-lg",
"input_className": "border-primary",
"label_className": "text-sm font-medium",
"columns": { "default": 12, "md": 6 }
}
Related Fields
- Select - Styled select with custom trigger and items
- Combobox - Searchable select
- Radio - Visible options
Accessibility
- Native
<select>element for full accessibility - Works with all assistive technologies
- Keyboard navigation built-in
- Mobile-friendly with native picker UI