Skip to the content

Kongens Nytorv

Metro M1, M2 and M3.

Transport mode

Props

NameTypeDefaultDescription
items*readonly SelectGroup[] | readonly SelectItem[]—Required. The options, in order: each with a value that identifies it and the label shown in its row and in the box once chosen, and disabled for one that can't be chosen; or groups, each with a label and its own items.
placeholderstring—Words shown in the box while no option is chosen, such as "Choose a transport mode". They never replace the label.
valuestring | null—The chosen option's value, or null for none, when the caller controls it.
defaultValuestring | null—The option chosen at first, when Select keeps the value itself. Left out, nothing is chosen.
onChange(event: Event, value: string | null, details: Select.Root.ChangeEventDetails) => void—Called when the chosen option changes: by a press or Enter on a row (details.reason 'item-press'); or, with 'none', by letters typed on the closed box, by the browser's autofill, or when the chosen option leaves items, which puts back the value given at first, while that option is still in items, or else null. event is details.event.
openboolean—Whether the list of options is shown, when the caller controls it.
defaultOpenbooleanfalseWhether the list is shown at first, when open is not set.
onOpen(event: Event, details: Select.Root.OpenChangeEventDetails) => void—Called when the list asks to open: by a press of the box, Enter or Space (details.reason 'trigger-press'), or an arrow key ('list-navigation').
onClose(event: Event, reason: Select.Root.ChangeEventReason, details: Select.Root.OpenChangeEventDetails) => void—Called when the list asks to close: reason is 'item-press', 'escape-key', 'outside-press', 'trigger-press', 'focus-out', 'cancel-open' or another of Base UI's reasons; details.cancel() keeps it open.
requiredbooleanfalseWhether an option must be chosen before the form is submitted. Use it or optional, never both.
readOnlybooleanfalseWhether the chosen option is shown but can't be changed. The list still opens, so the person can see the options.
label*ReactNode—Required. The field's name, in sentence case, such as "Stop name": shown above the control, and the control's accessible name.
hiddenLabelbooleanfalseWhether the label is heard but not seen. Use it only where the screen around the field already says what it is for.
optionalbooleanfalseWhether the person may leave the field empty. Adds "(optional)" after the label's words, seen and heard as part of the control's name. Set it on the few fields a form doesn't ask for; Field draws no mark for a required one.
optionalLabelstringThe locale'sOverrides "(optional)", the mark after the label's words with optional.
descriptionReactNode—A line of help under the label, such as "As written on the stop sign", read after the label by a screen reader. Left out, no description is rendered.
errorReactNode—What is wrong with the value and how to put it right, such as "Enter a stop name". Given, it marks the field invalid and is shown under the control; left out, the field shows Base UI's own message when the control fails its checks.
refRef<HTMLDivElement>—Reaches the root, the Field.Root <div>.
slotProps{ label?: FieldLabelProps | undefined; description?: FieldDescriptionProps | undefined; error?: FieldErrorProps | undefined; trigger?: SelectTriggerProps | undefined; value?: SelectValueProps | undefined; icon?: SelectIconProps | undefined; portal?: SelectPortalProps | undefined; positioner?: Omit<SelectPositionerProps, "alignItemWithTrigger"> | undefined; popup?: SelectPopupProps | undefined; list?: SelectListProps | undefined; group?: SelectGroupProps | undefined; groupLabel?: SelectGroupLabelProps | undefined; separator?: SelectSeparatorProps | undefined; item?: SelectItemProps | undefined; itemText?: SelectItemTextProps | undefined; itemIndicator?: SelectItemIndicatorProps | undefined; }—Props for each part: label, description, error, trigger, value, icon, portal, positioner, popup, list, group, groupLabel, separator, item, itemText, itemIndicator.
items (required)readonly SelectGroup[] | readonly SelectItem[], default —
Required. The options, in order: each with a value that identifies it and the label shown in its row and in the box once chosen, and disabled for one that can't be chosen; or groups, each with a label and its own items.
placeholderstring, default —
Words shown in the box while no option is chosen, such as "Choose a transport mode". They never replace the label.
valuestring | null, default —
The chosen option's value, or null for none, when the caller controls it.
defaultValuestring | null, default —
The option chosen at first, when Select keeps the value itself. Left out, nothing is chosen.
onChange(event: Event, value: string | null, details: Select.Root.ChangeEventDetails) => void, default —
Called when the chosen option changes: by a press or Enter on a row (details.reason 'item-press'); or, with 'none', by letters typed on the closed box, by the browser's autofill, or when the chosen option leaves items, which puts back the value given at first, while that option is still in items, or else null. event is details.event.
openboolean, default —
Whether the list of options is shown, when the caller controls it.
defaultOpenboolean, default false
Whether the list is shown at first, when open is not set.
onOpen(event: Event, details: Select.Root.OpenChangeEventDetails) => void, default —
Called when the list asks to open: by a press of the box, Enter or Space (details.reason 'trigger-press'), or an arrow key ('list-navigation').
onClose(event: Event, reason: Select.Root.ChangeEventReason, details: Select.Root.OpenChangeEventDetails) => void, default —
Called when the list asks to close: reason is 'item-press', 'escape-key', 'outside-press', 'trigger-press', 'focus-out', 'cancel-open' or another of Base UI's reasons; details.cancel() keeps it open.
requiredboolean, default false
Whether an option must be chosen before the form is submitted. Use it or optional, never both.
readOnlyboolean, default false
Whether the chosen option is shown but can't be changed. The list still opens, so the person can see the options.
label (required)ReactNode, default —
Required. The field's name, in sentence case, such as "Stop name": shown above the control, and the control's accessible name.
hiddenLabelboolean, default false
Whether the label is heard but not seen. Use it only where the screen around the field already says what it is for.
optionalboolean, default false
Whether the person may leave the field empty. Adds "(optional)" after the label's words, seen and heard as part of the control's name. Set it on the few fields a form doesn't ask for; Field draws no mark for a required one.
optionalLabelstring, default The locale's
Overrides "(optional)", the mark after the label's words with optional.
descriptionReactNode, default —
A line of help under the label, such as "As written on the stop sign", read after the label by a screen reader. Left out, no description is rendered.
errorReactNode, default —
What is wrong with the value and how to put it right, such as "Enter a stop name". Given, it marks the field invalid and is shown under the control; left out, the field shows Base UI's own message when the control fails its checks.
refRef<HTMLDivElement>, default —
Reaches the root, the Field.Root <div>.
slotProps{ label?: FieldLabelProps | undefined; description?: FieldDescriptionProps | undefined; error?: FieldErrorProps | undefined; trigger?: SelectTriggerProps | undefined; value?: SelectValueProps | undefined; icon?: SelectIconProps | undefined; portal?: SelectPortalProps | undefined; positioner?: Omit<SelectPositionerProps, "alignItemWithTrigger"> | undefined; popup?: SelectPopupProps | undefined; list?: SelectListProps | undefined; group?: SelectGroupProps | undefined; groupLabel?: SelectGroupLabelProps | undefined; separator?: SelectSeparatorProps | undefined; item?: SelectItemProps | undefined; itemText?: SelectItemTextProps | undefined; itemIndicator?: SelectItemIndicatorProps | undefined; }, default —
Props for each part: label, description, error, trigger, value, icon, portal, positioner, popup, list, group, groupLabel, separator, item, itemText, itemIndicator.

Use it

import { Select } from '@mylder/perron'

Ask your agent

get_reference({ library: "base-ui", component: "Select" })

Keyboard and ARIA

From the Base UI reference for Select and the installed SelectRoot, SelectTrigger, SelectPopup, SelectList, SelectItem, SelectGroup, SelectGroupLabel and SelectSeparator, and Field's for the label. "From" says who does it.

KeyDoesFrom
Enter, SpaceOn the closed trigger: open the list (onOpen, 'trigger-press'), highlighting and focusing the chosen option, or the first. On a highlighted row: choose it (onChange, 'item-press'), close the list (onClose, 'item-press') and return focus to the triggerBase UI
ArrowDown, ArrowUpOn the closed trigger: open the list (onOpen, 'list-navigation'), highlighting and focusing the chosen option, or, with nothing chosen, the first on ArrowDown and the last on ArrowUp. In the open list: move the highlight and focus to the next or previous row, disabled rows included, stopping at the first and the lastBase UI
Home, EndIn the open list: move the highlight and focus to the first or the last rowBase UI
Typing lettersOn the closed trigger: choose the next option whose label starts with them (onChange, 'none'), without opening the list. In the open list: move the highlight to it. Disabled options are skippedBase UI
EscapeIn the open list: close it (onClose, 'escape-key') and return focus to the trigger, keeping the valueBase UI
Tab, Shift+TabOn the trigger: move focus out of the field, the trigger being its one tab stop. In the open list: close it (onClose, 'focus-out') and return focus to the trigger, so a second Tab leaves the fieldBase UI
A press on the labelFocuses the trigger, without opening the list (Field's label, with nativeLabel={false})Base UI
ElementRoleAccessible name fromStates announced
labelnone: a <div>, or a hidden spanIts text: the caller's label, then "(optional)" with optionalnone
triggercombobox, from Base UI, on a native <button>aria-labelledby the labelaria-expanded, aria-haspopup="listbox", aria-required, aria-readonly, aria-invalid, disabled; its value is its text, the chosen option's label or the placeholder
value, iconnone: <span>s; the icon aria-hidden from Base UInonenone
listlistbox, from Base UIaria-labelledby the label, which Perron setsaria-readonly while read-only
groupgroup, from Base UIaria-labelledby its groupLabel, from Base UInone
groupLabelnone: a <div>, aria-hidden from Base UInone: heard as the group's name onlynone
separatorpresentation, from Base UInonenone
itemoption, from Base UIIts text: the item's labelaria-selected, aria-disabled; focused while highlighted
itemIndicatornone: the SelectedMark's aria-hidden ticknone: the row's aria-selected says itnone
positioner, popupnone, and presentation, from Base UInonenone
Base UI's hidden <input>none: aria-hidden, tabindex="-1"nonenone
description, errornone, as Field gives themnoneRead as the trigger's description, through aria-describedby, while mounted

What axe won't catch, from the contract, each with how this component meets it:

  • Everything the eye is told, the ear is told, for every shape each prop accepts: the chevron is aria-hidden beside a trigger the combobox role and aria-expanded describe; the selected mark is aria-hidden on a row whose aria-selected says it is chosen; a hidden label still names the trigger and the list; a group label is heard as its group's name; a disabled row is aria-disabled, not only grey; an invalid field is aria-invalid and its error is words, never only the trigger's red edge.
  • Every landmark and named region has an accessible name: none here; Select is not a landmark, and its list is named by the label.
  • Every default label can be overridden: optionalLabel, Field's, overrides "(optional)", the one owned string Select shows (see Owned strings).
  • Every shape the type accepts renders, in every variant: label, description and error render a string, an element and a list alike, as Field's do; items renders a flat list and groups alike; a long Danish compound wraps in a row and a group label and ends in an ellipsis in the box (CR-11).
  • A hidden copy kept for animation is aria-hidden, and a collapsed region is inert: none here; the closed list renders nothing, and Base UI makes the positioner inert while it closes; Base UI's hidden input is aria-hidden.
  • Native semantics first: a native <button> for the trigger, and Base UI's combobox, listbox, group and option roles; Perron adds only the listbox's aria-labelledby. The label is a <div> rather than a <label>, as Base UI advises for a <button> control, and names the trigger through aria-labelledby.
  • Focus moves in reading order and returns on close: the trigger is the field's one tab stop; opening moves focus to the chosen row, and choosing, Esc, Tab and a press outside return it to the trigger, through Base UI's popup.
  • Every target is at least size.target-min square: a row, at least size.target-min tall and as wide as the list; the trigger, size.input tall and as wide as the field.
  • Loops stop when the system asks for less motion: none here; Select has no loop and no transition (CR-12).

Owned strings

KeyEnglishOverrideSeen or heard where
optional"(optional)"optionalLabeloptional, Field's, seen after the label's words with optional, and heard in the trigger's name, as "Transport mode (optional)"

optional is the key packages/perron/src/locale/en.ts already has, "A field's label: after the words of a field the person may leave empty", which Field reads and Select passes optionalLabel to. Select adds no key.

Every other word is the caller's, through label, description, error, placeholder and items, or Base UI's message for a failing check, which is the caller's validate result or the browser's own, Field's known exception: a required select with nothing chosen fails the hidden input's native check, whose message the browser writes in its own interface language, unless the caller gives error or slotProps.error.match with its own words.

Do and don't

For a caller using the component, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.

RuleLevelDoDon'tChecked by
DD-1shouldUse Select for one choice from a short, fixed list, such as a transport mode or an orderUse it to find a line or a stop among hundreds: use SearchField, which narrows the list as the person typesreview
DD-2mustGive a label that names the value, "Transport mode", and hide it with hiddenLabel only where the screen already says itLet the placeholder stand in for the label: it is gone once an option is chosen, and drawn in color.text.tertiary, below text contrastreview
DD-3mustWrite the placeholder as an instruction, "Choose a transport mode"Put an option's words in the placeholder: the person reads it as a value already chosenreview
DD-4shouldGive short labels that the person can tell apart by their first letters, so typing on the closed box finds themStart every option with the same word ("Line M1", "Line M2"): typing finds nothing, and the box's ellipsis cuts the words that differreview
DD-5mustGroup options with SelectGroup when the list mixes kinds, such as metro and S-tog lines, and write each group's label in sentence caseWrite a group label in capitals: its style sets the capitals, and the ear hears the words as writtenreview
DD-6mustSet required or optional, whichever the form needsSet both: the field is marked optional while the form refuses it emptyreview
DD-7mustGive error whenever the field is invalidSet invalid with no message: the trigger's red edge alone tells only the eye, and only by its colourreview
DD-8shouldLeave an option that can't be chosen out of items, or give it disabled when the person should see it is thereDisable the whole field to say one option isn't availablereview
DD-9shouldKeep the field in a PerronProvider with the screen's locale, so "(optional)" speaks itTranslate it with optionalLabel on every instance: that is for one instance's own wordsreview