A select lets the user choose one option from a list. It looks and behaves like an input field, with a dropdown arrow. It opens the ECO Menu as its list.
Tokens, sizes, measurements, states and requirements. Values come from the live token package and from Figma: Base Input.
Resolved from the live token package.
| State | Part | Token | Value |
|---|
Hover is border-hover (#333333, the same value as border-dark). Active is read from the chosen option with :has(), so no script is needed.
| Breakpoint · size | Height | Padding (left, right) | Arrow | Label | Select text |
|---|---|---|---|---|---|
| Desktop · Large | 48px | 12px, 40px | 24px | label-md 16/16, 0.48px | body-md 17/24, 0.32px |
| Desktop · Small | 40px | 8px, 40px | 24px | label-sm 14/14, 0.56px | body-sm 14/20, 0.36px |
| Desktop · XSmall | 32px | 8px, 40px | 24px | label-sm 14/14, 0.56px | body-sm 14/20, 0.36px |
| Mobile · Large | 40px | 8px, 40px | 24px | label-sm 12/12, 0.48px | body-md 16/22, 0.32px |
| Mobile · Small | 32px | 8px, 40px | 24px | label-sm 12/12, 0.48px | body-sm 14/20, 0.36px |
Padding of the select at each size. The shaded areas are the padding to the left and right (40px on the right leaves room for the arrow); the box is fixed in height. The numbers are read from the component.
| Attribute | Large | Small | XSmall |
|---|---|---|---|
| Height, Desktop | 48px | 40px | 32px |
| Height, Mobile | 40px | 32px | Not available (use Small) |
| Padding left, Desktop | 12px | 8px | 8px |
| Padding left, Mobile | 8px | 8px | Not available |
| Padding right | 40px: room for the arrow | ||
| Arrow | 24px icon, 12px from the right edge, centered vertically | ||
| Border | 1px | ||
| Corners | Square | ||
The same padding and distances as spacing tokens.
| Part | Value |
|---|---|
| Label to box | space-4 |
| Box to message | space-4 |
| Padding left, Desktop Large | space-12 |
| Padding left, otherwise | space-8 |
| Padding right | 40px: literal, no token |
| Arrow from the right edge | space-12 |
| Menu below the field | space-4 |
| Focus ring | 2px line, 3px outside the box |
The seven states from Figma, plus Open (the menu is open: black border and the arrow turned 180°). Hover, focus and open are forced on this page; in your code they come from :hover, keyboard focus and aria-expanded="true". The open list itself is shown on the Menu page.
| State | No value | Value chosen |
|---|
Active means an option is chosen. Click a field to open its Menu. Active without a value is not defined: it shows a dash.
| Part | Type | Size / line height | Letter spacing | Weight |
|---|---|---|---|---|
| Label, Desktop Large | label-md | 16/16 | 0.48px | Bold, uppercase |
| Label, Desktop Small, XSmall | label-sm | 14/14 | 0.56px | Bold, uppercase |
| Label, Mobile | label-sm | 12/12 | 0.48px | Bold, uppercase |
| Select text, Large | body-md | 16/22 mobile, 17/24 from 769px | 0.32px | Regular |
| Select text, Small, XSmall | body-sm | 14/20 | 0.36px | Regular |
| Message | body-sm | 14/20 | 0.36px | Regular |
Breuer Condensed with ss02 and ss03 (the select text and the message also ss06).
| Element | Class | Notes |
|---|---|---|
| Field | form-field | Label, wrapper and message in a column, 4px apart. |
| Label | form-label, form-label--sm | Uppercase. --sm for Small and XSmall. |
| Wrapper | input-wrap | Carries the focus ring, the arrow and the open menu. |
| Select | form-select, --sm, --xs, --error, --success | The native select, appearance: none. |
| Arrow | form-select-arrow | Material Symbols arrow_drop_down, 24px. |
| Message | form-helper, --error, --success, --disabled | Optional icon (form-helper-icon). |
| Menu | menu, menu--sm | Added under the wrapper by select-menu.js while open. |
There is one type. The Menu size follows the field: Large opens a Large menu, Small and XSmall open a Small menu.
The field fills its container. The menu is as wide as the field.
| Property | Duration | Easing |
|---|---|---|
border-color of the select | duration-fast-3 | ease-standard |
opacity of the focus ring | duration-fast-3 | ease-standard |
| Arrow rotation (180° when the menu is open) | duration-fast-3 | ease-standard |
| Menu opens: fade in and move 8px away from the field | duration-medium-1 | ease-decelerate-generic |
| Menu closes: fade out and move 8px back | duration-fast-2 | ease-accelerate-generic |
The menu opens downwards, or upwards when it has flipped; the 8px move follows that direction. Under prefers-reduced-motion it opens and closes instantly.
| Input | Result |
|---|---|
| Click | Opens the Menu under the field (no ring on mouse) and the arrow turns 180° clockwise; it turns back when the menu closes. Click a row to choose; click outside to close. |
| Tab | The blue ring shows on keyboard focus only (body.keyboard-nav). |
| Enter, Space, Arrow keys | Open the Menu on the chosen row. Up and Down, Home and End move; Enter or Space choose; Escape closes and returns focus to the field; Tab closes. |
| Placeholder | A first option with value="" (disabled, hidden) shows the placeholder in text-tertiary. |
| Menu position | Under the field. It flips upwards when it does not fit below and there is more room above; otherwise its height is capped and it scrolls. |
| Disabled | Not focusable. Grey fill, light border, text-disabled text and arrow. |
| Topic | Requirement |
|---|---|
| Label | Always a label with for and id. A placeholder is never the label. |
| Error | Set aria-invalid="true" and point aria-describedby at the message. |
| List | The Menu is a listbox of option rows, with aria-selected, named by the label. The select gets aria-expanded while it is open. |
| Focus | A 2px ring on keyboard focus (border-focus), not on mouse click (WCAG 2.4.7, 1.4.11). Escape returns focus to the field. |
| Color | Error and success are not shown by color alone: the message says it, and the error message has an icon. |
| Disabled | Not focusable. Disabled tokens are exempt from contrast requirements. |