Select
An intelligent dropdown component with seamless form integration, automatic error handling, and options-based API built on TextField.
Quick Start
Copy & Paste
import { Select } from '@dashforge/ui';
<Select
label="Country"
name="country"
options={[
{ value: 'us', label: 'United States' },
{ value: 'uk', label: 'United Kingdom' }
]}
/>Examples
Common Select patterns and configurations
Basic
Single selection from a list of options
<Select
label="Country"
name="country"
options={[
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'mx', label: 'Mexico' },
]}
/>Disabled
Prevent user interaction when read-only
<Select
label="Country"
name="country"
disabled
options={[
{ value: 'us', label: 'United States' },
]}
/>Error State
Validation feedback for required selections
Please select a country
<Select
label="Country"
name="country"
error
helperText="Please select a country"
options={[
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
]}
/>Full Width
Expand to fill available container width
<Select
label="Country"
name="country"
fullWidth
options={[
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'mx', label: 'Mexico' },
]}
/>Multiple Options
Dropdown with extensive option lists
<Select
label="State"
name="state"
options={[
{ value: 'ny', label: 'New York' },
{ value: 'ca', label: 'California' },
{ value: 'tx', label: 'Texas' },
{ value: 'fl', label: 'Florida' },
{ value: 'il', label: 'Illinois' },
]}
/>With Placeholder
Guide users before they make a selection
<Select
label="Country"
name="country"
placeholder="Choose a country"
options={[
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'mx', label: 'Mexico' },
]}
/>Reactive V2 Examples
These examples demonstrate runtime-driven options through Reactive V2. Options are loaded dynamically via reactions, supporting async data fetching and generic option shapes.
Runtime-Driven Options
Load options from runtime state via Reactive V2. This example loads department options with a simulated async call and disables inactive options.
<DashForm
defaultValues={{ department: '' }}
reactions={[
{
id: 'load-departments',
watch: [],
run: async (ctx) => {
ctx.setRuntime('department', { status: 'loading' });
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 500));
ctx.setRuntime('department', {
status: 'ready',
data: {
options: [
{ id: 'eng', name: 'Engineering', active: true },
{ id: 'sales', name: 'Sales', active: true },
{ id: 'support', name: 'Support', active: false }
]
}
});
}
}
]}
>
<Select
name="department"
label="Department"
fullWidth
optionsFromFieldData="department"
getOptionValue={(opt) => opt.id}
getOptionLabel={(opt) => opt.name}
getOptionDisabled={(opt) => !opt.active}
/>
</DashForm>Layout Variants
Floating, stacked, and inline label layouts
Select supports three layout modes: floating (default), stacked, and inline. The layout prop controls label positioning and composition independently of the MUI variant (outlined/filled/standard).
Floating (Default)
Select your country
<Select
label="Country"
name="country"
options={options}
helperText="Select your country"
fullWidth
/>What to observe: Standard MUI floating label behavior. Label animates up when field is focused or has value. This is the migration-friendly default.
Stacked Layout
Select your country
<Select
label="Country"
name="country"
options={options}
helperText="Select your country"
layout="stacked"
fullWidth
/>What to observe: Label rendered above the input as a static label. This layout is ideal for forms where you want clear vertical hierarchy and maximum label visibility.
Inline Layout
Select your country
<Select
label="Country"
name="country"
options={options}
helperText="Select your country"
layout="inline"
fullWidth
/>What to observe: Label positioned to the left of the input. This layout is ideal for dense forms or settings panels where horizontal space is available.
Layout Comparison
<Select
label="Priority"
name="priority"
options={[
{ value: 'low', label: 'Low' },
{ value: 'medium', label: 'Medium' },
{ value: 'high', label: 'High' },
]}
fullWidth
/><Select
label="Priority"
name="priority"
options={[
{ value: 'low', label: 'Low' },
{ value: 'medium', label: 'Medium' },
{ value: 'high', label: 'High' },
]}
layout="stacked"
fullWidth
/><Select
label="Priority"
name="priority"
options={[
{ value: 'low', label: 'Low' },
{ value: 'medium', label: 'Medium' },
{ value: 'high', label: 'High' },
]}
layout="inline"
fullWidth
/>Interactive Playground
Experiment with props and see live results
Live Playground
Select your country
tsx
<Select
label="Country"
placeholder="Choose a country"
helperText="Select your country"
name="fieldName"
options={options}
/>Dashforge Capabilities
Progressive adoption from controlled components to predictive forms
Select is designed for progressive adoption. Use it as a simple controlled component, integrate it with React Hook Form, or leverage Reactive V2 for runtime-driven options. Choose the level that fits your team's workflow.
Controlled
Available Now
Select works as a standard React controlled component with familiar patterns. No proprietary lock-in required.
Standard value and onChange props
Low adoption friction for existing codebases
Suitable for incremental migration
<Select
value={country}
onChange={(e) => setCountry(e.target.value)}
options={countries}
label="Country"
/>React Hook Form Ready
Integration-Friendly
Designed to integrate with React Hook Form workflows through DashForm. Compatible with existing form-library patterns.
Works with RHF through DashFormBridge
Automatic validation and error handling
Supports gradual adoption without rewrites
<DashForm>
<Select
name="country"
label="Country"
options={countries}
/>
</DashForm>Reactive V2
Available Now
Select can load options dynamically from form state or external data. Supports conditional visibility and runtime-driven option loading.
Runtime options via optionsFromFieldData
Mapper functions for custom shapes
Conditional visibility with visibleWhen
<Select
name="city"
label="City"
optionsFromFieldData="city"
getOptionValue={(opt) => opt.id}
getOptionLabel={(opt) => opt.name}
visibleWhen={(engine) =>
engine.getNode('country')?.value !== ''
}
/>Access Control (RBAC)
Control field visibility and interaction based on user permissions. Fields can be hidden, disabled, or readonly when users lack access.
Hide when unauthorized
<Select
name="department"
label="Department"
access={{
resource: 'employee.department',
action: 'edit',
onUnauthorized: 'hide'
}}
options={[
{ value: 'eng', label: 'Engineering' },
{ value: 'sales', label: 'Sales' }
]}
/>Disable when cannot edit
<Select
name="priority"
label="Priority"
access={{
resource: 'project.priority',
action: 'edit',
onUnauthorized: 'disable'
}}
options={[
{ value: 'low', label: 'Low' },
{ value: 'high', label: 'High' }
]}
/>Readonly for view-only
<Select
name="status"
label="Status"
access={{
resource: 'contract.status',
action: 'edit',
onUnauthorized: 'readonly'
}}
options={[
{ value: 'draft', label: 'Draft' },
{ value: 'approved', label: 'Approved' }
]}
/>Combined with visibleWhen
<Select
name="expediteReason"
label="Expedite Reason"
visibleWhen={(e) =>
e.getValue('type') === 'expedited'
}
access={{
resource: 'order.expedite',
action: 'edit',
onUnauthorized: 'readonly'
}}
options={[
{ value: 'urgent', label: 'Urgent' }
]}
/>Note: When combining visibleWhen with RBAC, both conditions must be satisfied. The field shows only if UI logic returns true AND the user has required permissions.
Form Integration
Real-world scenarios with React Hook Form and dynamic visibility
Select works in real form contexts, not just isolated demos. Try these live scenarios to experience DashForm integration and reactive visibility—both fully implemented and production-ready.
React Hook Form Integration
Try it: Select options and submit the form
Select integrates seamlessly with React Hook Form through DashForm. Components self-register, errors display automatically, and validation follows familiar RHF patterns. Try submitting without making selections to see validation in action.
import { DashForm } from '@dashforge/forms';
import { Select } from '@dashforge/ui';
function PreferencesForm() {
const handleSubmit = (data: FormData) => {
console.log('Submitted:', data);
};
return (
<DashForm
defaultValues={{ country: '', language: '' }}
onSubmit={handleSubmit}
mode="onBlur"
>
<Select
name="country"
label="Country"
rules={{ required: 'Please select a country' }}
options={[
{ value: 'us', label: 'United States' },
{ value: 'uk', label: 'United Kingdom' },
{ value: 'ca', label: 'Canada' },
]}
/>
<Select
name="language"
label="Language"
rules={{ required: 'Please select a language' }}
options={[
{ value: 'en', label: 'English' },
{ value: 'es', label: 'Spanish' },
{ value: 'fr', label: 'French' },
]}
/>
<button type="submit">Submit</button>
</DashForm>
);
}
// Select automatically:
// - Registers with React Hook Form
// - Syncs value from form state
// - Displays validation errors when touched
// - Tracks dirty/touched stateWhy it matters
Gradual adoption: Drop Select into existing form architectures without rewriting validation logic or state management.
Conditional Field Visibility
Try it: Select a shipping method and watch fields appear
Select supports reactive visibility through the visibleWhen prop. Fields conditionally render based on Select values, enabling dynamic form flows without manual state orchestration. Choose "Express Shipping" to see the delivery date picker appear instantly.
import { DashForm } from '@dashforge/forms';
import { Select, TextField } from '@dashforge/ui';
function ShippingForm() {
return (
<DashForm defaultValues={{ shippingMethod: '', deliveryDate: '' }}>
<Select
name="shippingMethod"
label="Shipping Method"
options={[
{ value: 'standard', label: 'Standard (5-7 days)' },
{ value: 'express', label: 'Express (2-3 days)' },
{ value: 'overnight', label: 'Overnight' },
]}
/>
{/* Delivery date: visible only for express/overnight */}
<TextField
name="deliveryDate"
label="Preferred Delivery Date"
type="date"
rules={{ required: 'Date is required' }}
visibleWhen={(engine) => {
const node = engine.getNode('shippingMethod');
return node?.value === 'express' || node?.value === 'overnight';
}}
/>
{/* Special instructions: visible only for overnight */}
<TextField
name="specialInstructions"
label="Special Instructions"
multiline
rows={3}
visibleWhen={(engine) => {
const node = engine.getNode('shippingMethod');
return node?.value === 'overnight';
}}
/>
</DashForm>
);
}
// The Engine API provides:
// - getNode(name): Access any field's state
// - Reactive updates: Components re-render on dependency changes
// - Type-safe predicates: Full TypeScript supportWhy it matters
Move beyond static forms: Build adaptive workflows where field visibility responds to Select changes. The component handles reactivity—you define the rules.
Runtime-Driven Dependent Dropdowns
Try it: Select a country and watch cities load dynamically
Select supports dependent dropdowns through runtime options (Reactive V2). When one field changes, reactions can load new options for dependent fields. This example shows country/city selection with async option loading, loading states, and generic option shapes. Notice how changing countries demonstrates unresolved value behavior—the display clears but the form value remains unchanged.
import { DashForm } from '@dashforge/forms';
import { Select } from '@dashforge/ui';
interface City {
cityId: string;
cityName: string;
countryCode: string;
}
const MOCK_CITIES: City[] = [
{ cityId: 'nyc', cityName: 'New York', countryCode: 'us' },
{ cityId: 'tor', cityName: 'Toronto', countryCode: 'ca' },
{ cityId: 'lon', cityName: 'London', countryCode: 'uk' },
// ...more cities
];
function LocationForm() {
return (
<DashForm
defaultValues={{ country: '', city: '' }}
reactions={[
{
id: 'load-cities',
watch: ['country'],
when: (ctx) => ctx.getValue('country') !== '',
run: async (ctx) => {
const country = ctx.getValue<string>('country');
// Set loading state
ctx.setRuntime('city', {
status: 'loading',
data: null
});
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 800));
// Filter cities by country
const cities = MOCK_CITIES.filter(
c => c.countryCode === country
);
// Update runtime with options
ctx.setRuntime('city', {
status: 'ready',
data: { options: cities }
});
}
}
]}
>
<Select
name="country"
label="Country"
options={[
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'uk', label: 'United Kingdom' },
]}
/>
<Select
name="city"
label="City"
optionsFromFieldData="city"
getOptionValue={(opt: City) => opt.cityId}
getOptionLabel={(opt: City) => opt.cityName}
visibleWhen={(engine) =>
engine.getNode('country')?.value !== ''
}
/>
</DashForm>
);
}
// Reactive V2 enables:
// - Runtime-driven options via optionsFromFieldData
// - Generic option shapes via mapper functions
// - Loading states automatically handled
// - No automatic value reset (business data responsibility)Why it matters
Build complex forms with dependent data loading. The framework handles reactivity, loading states, and data flow—you define the dependencies. Unresolved values (when switching countries) display empty but maintain form integrity.
API Reference
Complete props and type definitions
| Prop | Type | Default | Description |
|---|---|---|---|
| name | string | - | Field name for form integration (required) |
| options | SelectOption[] | - | Array of static options with value and label. When optionsFromFieldData is provided, runtime options take precedence over static options for rendering. |
| optionsFromFieldData | string | - | Runtime field name to load options from. When provided, options are loaded from the field runtime state (Reactive V2) instead of the static options prop. Enables async/reactive option loading through reactions. |
| getOptionValue | (option: T) => string | number | (opt) => opt.value | Extracts the value from each option object. Use when option shape differs from the default {value, label} structure. Required when using generic option shapes with optionsFromFieldData. |
| getOptionLabel | (option: T) => string | (opt) => opt.label | Extracts the display label from each option object. Use when option shape differs from the default {value, label} structure. Required when using generic option shapes with optionsFromFieldData. |
| getOptionDisabled | (option: T) => boolean | (opt) => false | Determines if an option should be disabled. Optional mapper function that works with both static options and runtime options. |
| label | string | - | Label text displayed above or beside the select |
| value | string | number | - | Controlled value of the select |
| onChange | (event) => void | - | Callback fired when the value changes |
| error | boolean | false | If true, the select displays an error state |
| helperText | string | - | Helper text displayed below the select |
| disabled | boolean | false | If true, the select is disabled |
| fullWidth | boolean | false | If true, the select takes up the full width of its container |
| placeholder | string | - | Placeholder text shown when no value is selected |
| variant | 'outlined' | 'filled' | 'standard' | 'outlined' | Visual style variant of the select field |
| layout | 'floating' | 'stacked' | 'inline' | 'floating' | Label positioning mode (floating, stacked above, or inline beside) |
| rules | ValidationRules | - | Validation rules for form integration |
| visibleWhen | (engine: Engine) => boolean | - | Conditional visibility predicate. When false, component renders null. Receives engine instance with access to all field state via getNode(name). Re-evaluates on dependency changes. |
Under the hood
How Select works internally
Form integration
Automatically binds to form state inside DashForm. No Controller, no manual wiring. Works as a standard MUI TextField (select mode) when used standalone.
Behavior model
Options array prop for cleaner, declarative code. Errors appear only after blur or submit. Supports runtime-driven options via Reactive V2—load from APIs, filter dynamically, use any data shape with getOptionValue/getOptionLabel.
Architecture
Built on MUI TextField with select mode. Fully typed with TypeScript. Purpose-built for categories, statuses, countries, and any single-selection dropdown.