# Select Field A form field for choosing a single option from a predefined list Section: components Docs: https://nebula.kaluza.com/components/select-field --- ## 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](/components/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](/components/radio) or [Combobox Field](/components/combobox-field) so the full list is visible. - **When multiple selections are allowed** - use [Combobox Field](/components/combobox-field), which supports multi-select. - **When the user needs to search or filter options** - use [Combobox Field](/components/combobox-field), which supports type-to-filter. - **To indicate that a field is unavailable** - the disabled state is not supported. Use [Summary List](/components/summary-list) to show values that cannot be edited, or conditionally hide the field if it is not relevant in the current context. ## Anatomy 1. **Label** 2. **Hint** 3. **Select input** 4. **Chevron icon** (indicates the field is interactive) 5. **Error message** 6. **Required/Optional indicator** ## Basic usage ```tsx const Example = () => { const [selectValue, setSelectValue] = useState('wind'); return ( newValue && setSelectValue(newValue) } options={[ { value: 'solar', label: 'Solar' }, { value: 'wind', label: 'Wind' } ]} /> ); } ``` ## 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. ```tsx ``` ### Options as children Render native `` elements as children for grouped lists or when you need more control over option rendering. Both patterns can be combined. ```tsx ``` ### 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](/patterns/forms). These features are shared across all Nebula form fields. ## Grouping Select fields are typically part of a larger form. See the [Form component](/components/form) and [form guidelines](/patterns/forms) for guidance on grouping and layout. ## Rules and guidance ### Usage - **SelectField** MUST NOT be used when multiple selections are required. Rationale: The native select element does not support an accessible multi-select pattern. Use ComboboxField instead. [id: selectfield.not-for-multi-select; testability: auto] - **SelectField** MUST NOT be passed the disabled prop. Rationale: The 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. [id: selectfield.no-disabled; testability: auto] - **SelectField** SHOULD be used when the option set contains seven or more items. Rationale: Fewer options are better presented as radio buttons, which show all choices without interaction. [id: selectfield.use-for-large-lists; testability: context] - **SelectField** SHOULD be replaced with plain text. For example in a SummaryList. When displaying a value the user cannot edit. Rationale: A field that looks interactive but cannot be changed is confusing. Plain text communicates the value without implying editability. [id: selectfield.non-editable-as-text; testability: manual] - **SelectField** SHOULD NOT be used when the user needs to search or filter options. Rationale: Users cannot type to filter in a native select. Use ComboboxField instead. [id: selectfield.not-for-search; testability: context] ### Content - **Label** MUST be present and describe the choice the user is making. [id: selectfield.label-required; testability: manual] - **Option labels** MUST be concise, descriptive, and distinguishable from one another without reading the full list. [id: selectfield.option-labels-descriptive; testability: manual] - **Options** SHOULD be ordered with the most commonly selected option first, then alphabetically within groups of equal frequency. Rationale: Putting the most common option first reduces the time to complete the field for most users. [id: selectfield.option-ordering; testability: manual] - **Hint** SHOULD be provided when the options may not be self-explanatory. [id: selectfield.hint-for-unfamiliar-options; testability: context] - **SelectField** SHOULD NOT include a blank or instructional first option such as "Select an option". Rationale: Required/optional state is indicated by the field label. A placeholder option forces an extra interaction and can be submitted as an empty value. [id: selectfield.no-placeholder-option; testability: manual] ### Interaction - **SelectField** WILL be fully operable by keyboard, including opening, navigating, and selecting options. This is handled by the native select element. Rationale: Native select elements provide keyboard access natively. Do not override default keyboard behaviour. [id: selectfield.keyboard-accessible; testability: manual] - **onValueChange** MAY be used instead of onChange when only the string value is needed, to avoid reading event.currentTarget.value manually. [id: selectfield.onvaluechange-callback; testability: auto] ### Validation - **Error message** MUST describe what the user needs to do to correct the selection, not just that an error occurred. [id: selectfield.error-describes-correction; testability: manual] - If SelectField is used inside a Form component: **Error message** SHOULD be supplied via the Form component's errors prop and name prop rather than the error prop directly. Rationale: Form context error handling ensures consistent error routing across the form without manual wiring. [id: selectfield.error-from-form-context; testability: auto] ### Composition - **SelectField** MUST receive options either via the options prop or as option/optgroup children, not both at the same time. Rationale: Mixing both patterns will result in duplicate or incorrectly ordered options. [id: selectfield.options-or-children; testability: auto] - **SelectField children** MUST be native option or optgroup elements only. [id: selectfield.children-native-elements; testability: auto] ## Accessibility ### 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](https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html) #### 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](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html) ### 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](https://www.w3.org/WAI/WCAG22/Understanding/on-input.html) #### 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](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html) [4.1.2 Name, Role, Value](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html) #### 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](https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html) ### 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 each test covers: - 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. What these statuses mean: - 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. 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. ## Properties ### SelectField Also accepts all props from `HTMLAttributes`, `SelectHTMLAttributes`. - `id` (type `string`, required). 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` (type `ReactNode`, optional). 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` (type `string`, required). Visible label for the field. It is associated with the input, so it forms the input's accessible name. - `name` (type `string`, optional). The `name` attribute passed to the input. Also used to look up the field's error from a surrounding `Form`. - `hint` (type `ReactNode`, optional). 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` (type `boolean`, optional, default `false`). 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` (type `'always' | 'never' | 'small'`, optional, default `'small'`). 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` (type `number`, optional, default `25`). 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` (type `number`, optional, default `40`). Extra width in pixels added to the width calculated from `characters`, to make room for icons or buttons inside the input. - `options` (type `SelectOption[]`, optional). Options to render in the select, each with a `value` and a display `label`. They are rendered before any `children`. - `onValueChange` (type `(newValue: string | undefined) => void`, optional). Called with the newly selected option's value when the selection changes. Runs in addition to any `onChange` handler. - `autoComplete` (type `string`, optional). 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` (type `string | number | readonly string[]`, optional). Value of the selected option when the select is controlled. Pair it with `onValueChange` or `onChange` to update it.