Octocat

Select

A native select component with consistent styling across browsers.

import { Select } from '@/components/select';

export default function SelectPreview() {
  return (
    <div className="w-90">
      <Select>
        <option value="1">Option 1</option>
        <option value="2">Option 2</option>
        <option value="3">Option 3</option>
      </Select>
    </div>
  );
}

Source Code

'use client';

import type { VariantProps } from 'cva';

import {
  InputGroup,
  InputPrefix,
  inputStyle,
} from '@/components/input';
import { cn } from '@/lib/utils/classnames';

interface SelectProps extends React.ComponentPropsWithRef<'select'> {
  invalid?: boolean;
  variant?: VariantProps<typeof inputStyle>['variant'];
}

const Select = ({ className, invalid, variant, ...props }: SelectProps) => {
  return (
    <select
      data-invalid={invalid}
      className={cn(
        inputStyle({ variant }),
        'appearance-none bg-position-[right_--spacing(2)_center] bg-size-[1em] bg-no-repeat pr-10',
        'bg-[url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij48cGF0aCBmaWxsPSJibGFjayIgZD0iTTMuNyA1LjNsNC4zIDQuMyA0LjMtNC4zLjcuNy01IDUtNS01eiIvPjwvc3ZnPg==")]',
        'dark:bg-[url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij48cGF0aCBmaWxsPSJ3aGl0ZSIgZD0iTTMuNyA1LjNsNC4zIDQuMyA0LjMtNC4zLjcuNy01IDUtNS01eiIvPjwvc3ZnPg==")]',
        className
      )}
      {...props}
    />
  );
};

const SelectGroup = InputGroup;

const SelectPrefix = InputPrefix;

export { Select, SelectGroup, SelectPrefix };

Features

  • Native Element: Uses the browser’s native select element for optimal mobile support
  • Consistent Styling: Normalized appearance across different browsers
  • Form Integration: Works seamlessly with form libraries and native form validation
  • Prefix Support: Optional prefix for additional context
  • Variants: Supports default and minimal visual styles
  • Validation: Built-in invalid state styling

Anatomy


          <SelectGroup>
  <SelectPrefix />
  <Select>
    <option>
  </Select>
</SelectGroup>
        

API Reference

Select

Extends the select element.

Prop Default Type Description
variant "default" "default""minimal" The visual style variant to use.
invalid false boolean Whether the select is in an invalid state.

SelectGroup

Extends the div element.

A wrapper component that provides proper spacing and layout for the select and its prefix.

SelectPrefix

Extends the div element.

Prop Default Type
asChild - boolean

Accessibility

The Select component uses the native <select> element, which provides excellent accessibility out of the box:

  • Full keyboard navigation support
  • Screen reader announcements
  • Mobile-friendly interaction
  • Native form integration

Examples

Simple

Basic usage with a list of options.

import { Select } from '@/components/select';

export default function SelectPreview() {
  return (
    <div className="w-90">
      <Select>
        <option value="1">Option 1</option>
        <option value="2">Option 2</option>
        <option value="3">Option 3</option>
      </Select>
    </div>
  );
}

Minimal

Using the minimal variant without borders.

import { Select } from '@/components/select';

export default function SelectMinimalPreview() {
  return (
    <div className="w-90">
      <Select variant="minimal">
        <option value="1">Option 1</option>
        <option value="2">Option 2</option>
        <option value="3">Option 3</option>
      </Select>
    </div>
  );
}

Disabled

The select in a disabled state.

import { Select } from '@/components/select';

export default function SelectDisabledPreview() {
  return (
    <div className="w-90">
      <Select disabled>
        <option value="1">Option 1</option>
        <option value="2">Option 2</option>
        <option value="3">Option 3</option>
      </Select>
    </div>
  );
}

Invalid

The select showing an invalid state.

import { Select } from '@/components/select';

export default function SelectInvalidPreview() {
  return (
    <div className="w-90">
      <Select invalid>
        <option value="1">Option 1</option>
        <option value="2">Option 2</option>
        <option value="3">Option 3</option>
      </Select>
    </div>
  );
}

Prefix

Using a prefix to provide additional context.

import { UserIcon } from '@phosphor-icons/react/dist/ssr';

import {
  Select,
  SelectGroup,
  SelectPrefix,
} from '@/components/select';

export default function SelectPrefixPreview() {
  return (
    <div className="w-90">
      <SelectGroup>
        <SelectPrefix>
          <UserIcon />
        </SelectPrefix>
        <Select>
          <option value="1">Option 1</option>
          <option value="2">Option 2</option>
          <option value="3">Option 3</option>
        </Select>
      </SelectGroup>
    </div>
  );
}

Best Practices

  1. Use Listbox for Complex Cases:

    • If you need custom option rendering
    • If you need search functionality
    • If you need multiple selection
    • If you need complex keyboard interactions
  2. Mobile Considerations:

    • The native select provides the best experience on mobile devices
    • It automatically adapts to the platform’s native picker
  3. Form Integration:

    • Use the required attribute for required fields
    • Use the name attribute for form submission
    • Use the invalid prop in conjunction with form validation
  4. Accessibility:

    • Always provide a visible label or aria-label
    • Group related selects with fieldset and legend
    • Use aria-describedby for additional descriptions

Previous

Radio

Next

Skeleton