Select | Dashforge-UI
DocsStarter Kits
v0.1.0-alpha

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.

Reactive V2
<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)
Live Preview

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
Live Preview

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
Live Preview

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
Floating
<Select
  label="Priority"
  name="priority"
  options={[
    { value: 'low', label: 'Low' },
    { value: 'medium', label: 'Medium' },
    { value: 'high', label: 'High' },
  ]}
  fullWidth
/>
Stacked
<Select
  label="Priority"
  name="priority"
  options={[
    { value: 'low', label: 'Low' },
    { value: 'medium', label: 'Medium' },
    { value: 'high', label: 'High' },
  ]}
  layout="stacked"
  fullWidth
/>
Inline
<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

Interactive Playground

Live Playground

Configuration
Presets
Properties
Layout
Variant
Live Preview

Select your country

Generated Code

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.

Live Preview
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 state

Why 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.

Live Preview
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 support

Why 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.

Live Preview
Try it: Select a country and watch the city field appear with loading state, then display cities for that country. If you switch countries after selecting a city, the display clears but the form value remains unchanged (no automatic reset).
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

PropTypeDefaultDescription
namestring-Field name for form integration (required)
optionsSelectOption[]-Array of static options with value and label. When optionsFromFieldData is provided, runtime options take precedence over static options for rendering.
optionsFromFieldDatastring-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.valueExtracts 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.labelExtracts 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) => falseDetermines if an option should be disabled. Optional mapper function that works with both static options and runtime options.
labelstring-Label text displayed above or beside the select
valuestring | number-Controlled value of the select
onChange(event) => void-Callback fired when the value changes
errorbooleanfalseIf true, the select displays an error state
helperTextstring-Helper text displayed below the select
disabledbooleanfalseIf true, the select is disabled
fullWidthbooleanfalseIf true, the select takes up the full width of its container
placeholderstring-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)
rulesValidationRules-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.


On This Page