Select

A real <select> with the native arrow replaced. Pass <option> elements as children; value, defaultValue, onChange, multiple and ref all pass straight through.

Props

PropValuesDefaultDescription
size"sm", "md", "lg""md"Height, padding, and text size.
invalidbooleanfalseApplies the danger border and sets aria-invalid.
classNamestring""Appended to the <select>, not the wrapper.

All other <select> attributes are forwarded.

Common patterns

<Select value={role} onChange={(e) => setRole(e.target.value)}>
  <option value="admin">Admin</option>
  <option value="member">Member</option>
</Select>

// Grouped options work as usual
<Select>
  <optgroup label="Staff">
    <option>Admin</option>
  </optgroup>
</Select>

Notes

Staying native is the point. The browser supplies the dropdown, which means it is correct on touch, correct with a screen reader, and has no positioning code that can misplace a menu near the edge of the viewport.

The cost is that you cannot style the options themselves — that surface belongs to the operating system. If you need styled option rows, you need a listbox built from <div>s, which is a different and much larger component.

The wrapper <span> exists only to position the arrow. className goes to the <select>, so sizing and colors work the way you'd expect.