Select

Material Design 3 select (dropdown) component with multiple variants and options

Select

Select components (also known as dropdowns) allow users to choose one or more options from a list. @duskmoon-dev/core provides a complete set of Material Design 3 select variants with support for various states and configurations.

Basic Usage

The basic select component uses the outlined variant by default:

Basic Select

With Label

Add a label to provide context for the select:

Select with Label

Variants

Outlined Select (Default)

The outlined variant has a transparent background with a visible border:

Outlined Select

Filled Select

The filled variant has a filled background with a bottom border, perfect for forms with a consistent visual style:

Filled Select

Filled Select with Floating Label

Create a modern filled select with a floating label:

Filled Select with Floating Label

Color Variants

Primary Select

Use primary color for the focus state:

Primary Select

Secondary Select

Use secondary color for the focus state:

Secondary Select

Tertiary Select

Use tertiary color for the focus state:

Tertiary Select

Sizes

Three sizes are available to fit different design needs:

Small

Small Select

Medium (Default)

Medium Select

Large

Large Select

Multiple Selection

Allow users to select multiple options:

Multiple Selection

Grouped Options

Organize options using optgroups:

Grouped Options

States

Disabled State

Disabled selects are non-interactive:

Disabled Select

Error State

Show validation errors:

Error State

This field is required

Loading State

Show a loading indicator:

Loading State

Helper Text

Provide additional context or instructions:

Select with Helper Text

This will be used for all communications

Full Width

Make the select take the full width of its container:

Full Width Select

Best Practices

Form Design

Combine selects with other form elements for a cohesive design:

Form Example

Choose your primary department

Accessibility

  • Always provide a <label> element associated with the select
  • Use the first option as a prompt (“Choose an option”)
  • Add aria-label or aria-labelledby if a visible label isn’t present
  • Ensure proper color contrast for all states
  • Support keyboard navigation (built-in)
  • Use helper text for additional context

Accessible Select

This helps us provide relevant content

Error Handling

Always provide clear error messages:

Error Handling Example

Please select a payment method

When to Use Multiple Select

Use multiple select sparingly as they can be difficult to use:

  • ✅ Good: When users need to select 3-7 related items
  • ❌ Avoid: When users need to select more than 7 items (use checkboxes instead)
  • ❌ Avoid: For single selection (use regular select)

Framework Examples

React

interface SelectProps {
  label?: string;
  helperText?: string;
  error?: boolean;
  variant?: 'outlined' | 'filled';
  size?: 'sm' | 'md' | 'lg';
  color?: 'primary' | 'secondary' | 'tertiary';
  disabled?: boolean;
  multiple?: boolean;
  children: React.ReactNode;
  value?: string | string[];
  onChange?: (e: React.ChangeEvent<HTMLSelectElement>) => void;
}

export function Select({
  label,
  helperText,
  error = false,
  variant = 'outlined',
  size = 'md',
  color = 'primary',
  disabled = false,
  multiple = false,
  children,
  value,
  onChange,
  ...props
}: SelectProps) {
  const containerClass = `select-container ${
    variant === 'filled' ? 'select-container-filled' : ''
  } ${error ? 'select-container-error' : ''}`;

  const selectClass = `select select-${variant} select-${color} select-${size} ${
    error ? 'select-error' : ''
  }`;

  return (
    <div className={containerClass}>
      {label && <label className="select-label">{label}</label>}
      <select
        className={selectClass}
        disabled={disabled}
        multiple={multiple}
        value={value}
        onChange={onChange}
        aria-invalid={error}
        {...props}
      >
        {children}
      </select>
      <span className="select-icon">▼</span>
      {helperText && <span className="select-helper">{helperText}</span>}
    </div>
  );
}

// Usage
function App() {
  const [country, setCountry] = useState('');

  return (
    <Select
      label="Country"
      value={country}
      onChange={(e) => setCountry(e.target.value)}
      helperText="Select your country of residence"
    >
      <option value="">Choose a country</option>
      <option value="us">United States</option>
      <option value="uk">United Kingdom</option>
      <option value="ca">Canada</option>
    </Select>
  );
}

Vue

<template>
  <div :class="containerClass">
    <label v-if="label" class="select-label">{{ label }}</label>
    <select
      :class="selectClass"
      :disabled="disabled"
      :multiple="multiple"
      :value="modelValue"
      @change="handleChange"
      :aria-invalid="error"
      v-bind="$attrs"
    >
      <slot />
    </select>
      <span v-if="helperText" class="select-helper">{{ helperText }}</span>
  </div>
</template>

<script setup>
import { computed } from 'vue';

const props = defineProps({
  label: String,
  helperText: String,
  error: {
    type: Boolean,
    default: false
  },
  variant: {
    type: String,
    default: 'outlined'
  },
  size: {
    type: String,
    default: 'md'
  },
  color: {
    type: String,
    default: 'primary'
  },
  disabled: {
    type: Boolean,
    default: false
  },
  multiple: {
    type: Boolean,
    default: false
  },
  modelValue: [String, Array]
});

const emit = defineEmits(['update:modelValue']);

const containerClass = computed(() => {
  return [
    'select-container',
    props.variant === 'filled' && 'select-container-filled',
    props.error && 'select-container-error'
  ].filter(Boolean).join(' ');
});

const selectClass = computed(() => {
  return [
    'select',
    `select-${props.variant}`,
    `select-${props.color}`,
    `select-${props.size}`,
    props.error && 'select-error'
  ].filter(Boolean).join(' ');
});

const handleChange = (e) => {
  emit('update:modelValue', e.target.value);
};
</script>

<!-- Usage -->
<template>
  <Select
    v-model="selectedCountry"
    label="Country"
    helper-text="Select your country of residence"
  >
    <option value="">Choose a country</option>
    <option value="us">United States</option>
    <option value="uk">United Kingdom</option>
    <option value="ca">Canada</option>
  </Select>
</template>

<script setup>
import { ref } from 'vue';

const selectedCountry = ref('');
</script>

Svelte

<script>
  export let label = '';
  export let helperText = '';
  export let error = false;
  export let variant = 'outlined';
  export let size = 'md';
  export let color = 'primary';
  export let disabled = false;
  export let multiple = false;
  export let value = '';

  $: containerClass = [
    'select-container',
    variant === 'filled' && 'select-container-filled',
    error && 'select-container-error'
  ].filter(Boolean).join(' ');

  $: selectClass = [
    'select',
    `select-${variant}`,
    `select-${color}`,
    `select-${size}`,
    error && 'select-error'
  ].filter(Boolean).join(' ');
</script>

<div class={containerClass}>
  {#if label}
    <label class="select-label">{label}</label>
  {/if}
  <select
    class={selectClass}
    {disabled}
    {multiple}
    bind:value
    aria-invalid={error}
    {...$$restProps}
  >
    <slot />
  </select>
  {#if helperText}
    <span class="select-helper">{helperText}</span>
  {/if}
</div>

<!-- Usage -->
<script>
  let selectedCountry = '';
</script>

<Select
  bind:value={selectedCountry}
  label="Country"
  helperText="Select your country of residence"
>
  <option value="">Choose a country</option>
  <option value="us">United States</option>
  <option value="uk">United Kingdom</option>
  <option value="ca">Canada</option>
</Select>

API Reference

Container Classes

ClassDescription
.select-containerBase container (required)
.select-container-filledContainer for filled variant
.select-container-errorContainer error state
.select-fullFull width container

Select Classes

ClassDescription
.selectBase select styles (required)
.select-outlinedOutlined variant (default)
.select-filledFilled variant
.select-primaryPrimary color focus
.select-secondarySecondary color focus
.select-tertiaryTertiary color focus
.select-smSmall size
.select-lgLarge size
.select-errorError state
.select-loadingLoading state with spinner

Label Classes

ClassDescription
.select-labelStandard label
.select-label-floatingFloating label for filled variant

Helper Classes

ClassDescription
.select-helperHelper text
.select-iconDropdown arrow icon

HTML Attributes

AttributeDescription
disabledDisables the select
multipleAllows multiple selections
sizeNumber of visible options (for multiple)
requiredMakes the field required
aria-invalidIndicates error state
aria-describedbyAssociates helper text

Combinations

Combined Styles

Please select a department
  • Input - Text input fields
  • Checkbox - Multiple choice selection
  • Radio - Single choice selection
  • Button - Action buttons for forms

See Also