On this page
Select Field
A form field for choosing a single option from a predefined list
- Overview
- Rules and guidance
- Properties
- Styles
- Accessibility
When to use
- When the user needs to choose one option from a large, familiar set (typically seven or more options)
- When the options are well known and the user does not need to read every item to make a choice, for example a list of countries or months
- When you need to save screen space compared to a full list of radio buttons
- When the options are mutually exclusive and only one can be selected
When not to use
- When there are fewer than seven options - use Radio instead, so users can see all choices at a glance without opening a dropdown.
- When users need to read all the options to decide - use Radio or Combobox Field so the full list is visible.
- When multiple selections are allowed - use Combobox Field, which supports multi-select.
- When the user needs to search or filter options - use Combobox Field, which supports type-to-filter.
- To indicate that a field is unavailable - the disabled state is not supported. Use Summary List to show values that cannot be edited, or conditionally hide the field if it is not relevant in the current context.
Anatomy
- Label
- Hint
- Select input
- Chevron icon (indicates the field is interactive)
- Error message
- Required/Optional indicator
Basic usage
Edit live code
() => { // ignore - for live code example only. const Example = () => { const [selectValue, setSelectValue] = useState('wind'); return ( <SelectField id="energy" label="Select a renewable energy source?" value={selectValue} onValueChange={(newValue) => newValue && setSelectValue(newValue) } options={[ { value: 'solar', label: 'Solar' }, { value: 'wind', label: 'Wind' } ]} /> ); } return <Example />; }
Variants and features
Options as a prop
Pass an array of { value, label } objects to the options prop. This is the simplest approach for flat lists.
Edit live code
<SelectField id="energy-2" label="Renewable energy source" options={[ { value: 'solar', label: 'Solar' }, { value: 'wind', label: 'Wind' }, { value: 'hydro', label: 'Hydro' }, { value: 'tidal', label: 'Tidal' }, { value: 'geothermal', label: 'Geothermal' }, { value: 'biomass', label: 'Biomass' }, ]} />
Options as children
Render native <option> and <optgroup> elements as children for grouped lists or when you need more control over option rendering. Both patterns can be combined.
Edit live code
<SelectField id="meter-type" label="Meter type"> <optgroup label="Standard"> <option value="single">Single rate</option> <option value="economy7">Economy 7</option> </optgroup> <optgroup label="Smart"> <option value="smets1">SMETS1</option> <option value="smets2">SMETS2</option> </optgroup> </SelectField>
Option ordering
Put the most commonly selected option first. Within groups of equally common options, use alphabetical order. Do not include a blank placeholder option such as "Select an option" to indicate the field is required. Use the required/optional indicator instead.
Common form features
For validation, hint text, required/optional labelling, and field width, see the Form guidance. These features are shared across all Nebula form fields.
Grouping
Select fields are typically part of a larger form. See the Form component and form guidelines for guidance on grouping and layout.
usage
- SelectField must not be used when multiple selections are requiredThe native select element does not support an accessible multi-select pattern. Use ComboboxField instead.Copy rule ID for selectfield.not-for-multi-select
- SelectField must not be passed the disabled propThe prop passes through to the native input with no Nebula styling and removes the field from the tab order, making it invisible to keyboard users with no accessible indication of why.Copy rule ID for selectfield.no-disabled
- SelectField should be used when the option set contains seven or more itemsFewer options are better presented as radio buttons, which show all choices without interaction.Copy rule ID for selectfield.use-for-large-lists
- SelectField should be replaced with plain text. For example in a SummaryList. When displaying a value the user cannot editA field that looks interactive but cannot be changed is confusing. Plain text communicates the value without implying editability.Copy rule ID for selectfield.non-editable-as-text
- SelectField should not be used when the user needs to search or filter optionsUsers cannot type to filter in a native select. Use ComboboxField instead.Copy rule ID for selectfield.not-for-search
content
- Label must be present and describe the choice the user is makingCopy rule ID for selectfield.label-required
- Option labels must be concise, descriptive, and distinguishable from one another without reading the full listCopy rule ID for selectfield.option-labels-descriptive
- Options should be ordered with the most commonly selected option first, then alphabetically within groups of equal frequencyPutting the most common option first reduces the time to complete the field for most users.Copy rule ID for selectfield.option-ordering
- Hint should be provided when the options may not be self-explanatoryCopy rule ID for selectfield.hint-for-unfamiliar-options
- SelectField should not include a blank or instructional first option such as "Select an option"Required/optional state is indicated by the field label. A placeholder option forces an extra interaction and can be submitted as an empty value.Copy rule ID for selectfield.no-placeholder-option
interaction
- SelectField will be fully operable by keyboard, including opening, navigating, and selecting options. This is handled by the native select elementNative select elements provide keyboard access natively. Do not override default keyboard behaviour.Copy rule ID for selectfield.keyboard-accessible
- onValueChange may be used instead of onChange when only the string value is needed, to avoid reading event.currentTarget.value manuallyCopy rule ID for selectfield.onvaluechange-callback
validation
- Error message must describe what the user needs to do to correct the selection, not just that an error occurredCopy rule ID for selectfield.error-describes-correction
- If SelectField is used inside a Form componentError message should be supplied via the Form component's errors prop and name prop rather than the error prop directlyForm context error handling ensures consistent error routing across the form without manual wiring.Copy rule ID for selectfield.error-from-form-context
composition
- SelectField must receive options either via the options prop or as option/optgroup children, not both at the same timeMixing both patterns will result in duplicate or incorrectly ordered options.Copy rule ID for selectfield.options-or-children
- SelectField children must be native option or optgroup elements onlyCopy rule ID for selectfield.children-native-elements
SelectField
Also accepts all props from HTMLAttributes, SelectHTMLAttributes.
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
id | string | — | Yes | Unique id for the input, used to link the label, hint and error to it. Also used as the base for related ids such as `{id}-error`. |
error | ReactNode | — | No | Error message shown below the input. When set, the input gets `aria-invalid` and an error border, and the message is linked to it with `aria-describedby`. Takes precedence over an error provided by a surrounding `Form`. |
label | string | — | Yes | Visible label for the field. It is associated with the input, so it forms the input's accessible name. |
name | string | — | No | The `name` attribute passed to the input. Also used to look up the field's error from a surrounding `Form`. |
hint | ReactNode | — | No | Helper text shown below the label. It is part of the label element, so it is read as part of the input's accessible name. |
optional | boolean | false | No | Marks the field as not required. This appends "(optional)" to the label (or, when the theme marks required fields, hides the required asterisk) and sets `aria-required` on the input only when the field is not optional. |
fullWidth | always never small | 'small' | No | Controls when the field stretches to the full width of its container. `always` is always full width, `never` always uses the width set by `characters`, and `small` is full width on small screens only. |
characters | number | 25 | No | Width of the input, as an approximate number of characters (the width is set to `0.8em` per character, rounded up). Does not limit how much can be typed. |
affixWidth | number | 40 | No | Extra width in pixels added to the width calculated from `characters`, to make room for icons or buttons inside the input. |
options | SelectOption[] | — | No | Options to render in the select, each with a `value` and a display `label`. They are rendered before any `children`. |
onValueChange | (newValue: string | undefined) => void | — | No | Called with the newly selected option's value when the selection changes. Runs in addition to any `onChange` handler. |
autoComplete | string | — | No | Hint to the browser's autofill, for example `email`. When the `disableAutocomplete` theme config flag is enabled this defaults to `off`; a value passed here overrides that. |
value | string | number | readonly string[] | — | No | Value of the selected option when the select is controlled. Pair it with `onValueChange` or `onChange` to update it. |
This component has no styles of its own to document.
In brief
Select Field is a native select inside Nebula's field wrapper. The browser and the screen reader announce the label, the selected option and the role, and they differ between browsers and operating systems.
What Nebula provides
- A native select: the browser and the operating system own the list, the keys, type-ahead and the selected option announcement.
- The label is linked to the select. A required field is marked as required to a screen reader, and the asterisk or "(optional)" text is shown according to the theme.
- An error marks the select invalid and is linked to it as a description. It shows an icon and text, not colour alone.
- The chevron is hidden from screen readers, and the select shows a focus ring on keyboard focus.
Keyboard behaviour
The select is reached with Tab. The keys that open the list and change the option are the browser's own and differ between browsers and operating systems.
Interactions
- Tab - Moves focus to the select
- Up or Down - Changes or moves between options, as the browser does
Accessible design
Keep the hint short or leave it out
The hint sits inside the label, so a screen reader reads it as part of the field's name every time the select gets focus. Put anything long above the field instead.
WCAG: 3.3.2 Labels or Instructions
Write option labels that make sense without their group
Not every screen reader announces the label of an optgroup, so "Hydro" has to be clear without "Electricity". Visual grouping alone does not reach those users.
WCAG: 1.3.1 Info and Relationships
Accessible development
Don't navigate, submit or change the page from onChange or onValueChange
On some platforms the arrow keys change the option without opening the list, so the handler runs on every step a keyboard user takes. Make the change take effect on a button instead.
WCAG: 3.2.2 On Input
Always pass a unique id
The id is what links the label, hint and error to the select. Without a unique one the select has no name, or takes the name of another.
WCAG: 1.3.1 Info and Relationships 4.1.2 Name, Role, Value
If answering is not required, set optional
Without it the select is marked as required to a screen reader, and the label does not say it is optional.
WCAG: 3.3.2 Labels or Instructions
Accessibility testing status
Reviewed on 9 October 2026 against v17.0.1.
| Test | Status | Tested with |
|---|---|---|
| Default state | Tested | axe-core via the Storybook test runner (CI) |
| Advanced states | Partially tested | axe-core via the Storybook test runner (CI) |
| Keyboard navigation | Manually tested | keyboard (reported by the owner) |
| Screen reader | Partially tested | VoiceOver with Chrome (reported by the owner) |
What these tests and statuses mean
The table records which checks were run on this component on its own. It is not a statement of WCAG conformance, and it does not cover how the component is used.
The tests
- Default state: The component as it first appears, before anything is clicked or changed.
- Advanced states: Every other state and variant: error, open, selected, each size and style, and hovering.
- Keyboard navigation: Whether every function can be reached and used with the keyboard alone, with a visible focus ring.
- Screen reader: Whether a screen reader announces what is on screen correctly: the name, the role and any state.
The statuses
- Tested: Every check in this row passed in an automated run that repeats on every pull request.
- Manually tested: Every check in this row was run once, by a person or with a tool they started, and all of them passed. It is not re-run on every change.
- Partially tested: Some checks in this row were run once and passed; the rest are still to do.
- Issue found: A check in this row failed. The ticket is shown beside the status.
- Not tested: No check in this row has been run yet.
- Not applicable: This row does not apply to the component, for the reason given beside it.