Select
Selects a single value from a known list without accepting text input.
Design
Functional preview
Running with stable DS HTML and CSS, without a component JavaScript runtime.
Open the HTML/CSS/JS playground
Anatomy
.ds-select) — container with border, radius.2 Icon (optional,
.ds-select__icon) — leading icon, decorative.3 Field (
.ds-select__field) — native <select> element.4 Arrow (
.ds-select__arrow) — custom chevron, pointer-events: none.5 Error message (
.ds-field__error) — external, via Form Field.6 Helper text (
.ds-field__helper) — external, via Form Field.7 Label (
.ds-field__label) — external, via Form Field.
Default
Sizes
| Size | Height | Font | Padding | Touch target |
|---|---|---|---|---|
Small (--sm) | 32px (component.select.height.sm) | component.select.text.font-size.sm | component.select.padding-x.sm / component.field.padding-y.sm | 32px — meets WCAG 2.5.8 min (24px) |
Medium (--md) | 40px (component.select.height.md) | component.select.text.font-size.md | component.select.padding-x.md / component.field.padding-y.md | 40px |
Large (--lg) | 48px (component.select.height.lg) | component.select.text.font-size.lg | component.select.padding-x.lg / component.field.padding-y.lg | 48px — meets AAA 44px target |
With icon
States
In Figma, State only represents exclusive visual states: Default, Hover, Focus, and Disabled. Filled, Error, and Read-only are separate properties; Error combines with Default/Hover/Focus, and Read-only combines with Default/Focus.
Error
Disabled
Readonly
| Figma property | Value / CSS trigger | Visual change | Token |
|---|---|---|---|
| State | Default | Neutral border | component.field.border-color.default |
| State | Hover / :hover | Darker border | component.field.border-color.hover |
| State | Focus / :focus-visible | 2px outline ring, component radius | component.field.border-color.focus, component.focus-ring.*, component.select.focus-ring.radius.default |
| State | Disabled / [disabled] | Muted bg + content, no pointer events | component.field.bg.disabled, component.field.value.color.disabled, component.field.placeholder.color.disabled |
| Error | True / .ds-select--error or .ds-field--error .ds-select | Red border and error focus ring | component.field.border-color.error, component.focus-ring.color.error |
| Error + State | True + Hover / .ds-select--error:hover | Darker red border | component.field.border-color.error-hover |
| Read-only | True / .ds-select--readonly + [disabled] | Readonly bg and content treatment; native select has no real readonly focus | component.field.bg.readonly, component.field.value.color.readonly |
Figma properties
| Property | Type | Default | Description |
|---|---|---|---|
Show Label | Boolean | true | Shows or hides the label row (including required asterisk) |
Label | Text | "Rótulo" | Label text (label/md) |
Required | Boolean | false | Shows * in feedback/error/content/default next to the label |
Show Helper Text | Boolean | true | Shows or hides the helper text below the control |
Helper Text | Text | "Texto auxiliar" | Caption/sm annotation (content/secondary) |
Usage
When to use
Best practices
<option value="" disabled selected>Choose a country</option>.Content guidelines
| Rule | Example |
|---|---|
| Placeholder: "Choose a..." or "Select a..." in sentence case | "Choose a country" -- not "CHOOSE A COUNTRY" or "Country" |
| Option text should be concise and consistent | "Brazil", "United States" -- not "The Federative Republic of Brazil" |
| Don't mix option types | All countries, or all languages -- not countries mixed with regions |
| Use sentence case for options | "Credit card" -- not "Credit Card" or "CREDIT CARD" |
Always pair with a <label> | <label for="country">Country</label> |
Related
Implementation
- Status
- Stable
- Distribution
- npm package
Installation
npm install ds-tispnpm add ds-tisyarn add ds-tisbun add ds-tisImports
import 'ds-tis/css'Markup
<div class="ds-field">
<label class="ds-field__label" for="country">País</label>
<div class="ds-select ds-select--md">
<select class="ds-select__field" id="country" name="country">
<option value="" disabled selected>Selecione um país</option>
<option value="br">Brasil</option>
<option value="cl">Chile</option>
</select>
<span class="ds-select__arrow" aria-hidden="true"></span>
</div>
</div>Web implementation contract
Token mapping
Select consumes Component tokens in CSS and Figma bindings. These tokens document the component's public contract and alias Semantic when the value is reusable.
| Property | Token Component | CSS variable |
|---|---|---|
| bg | component.field.bg.{default|focus|filled|error|disabled|readonly} | --ds-field-bg-* |
| border color | component.field.border-color.{default|hover|focus|filled|error|disabled|readonly} | --ds-field-border-color-* |
| border width | component.field.border-width | --ds-field-border-width |
| content color | component.field.value.color.{default|disabled|readonly} | --ds-field-value-color-* |
| placeholder color | component.field.placeholder.color.{default|disabled} | --ds-field-placeholder-color-* |
| chevron | component.field.icon.{color|size|stroke-width}.* + component.field.icon-frame.padding-x.default | --ds-field-icon-*, --ds-field-icon-frame-padding-x-default |
| height | component.select.height.{sm|md|lg} | --ds-select-height-* |
| gap | component.field.gap.{sm|md|lg} | --ds-field-gap-* |
| padding | component.select.padding-x.* / component.field.padding-y.* | --ds-select-padding-x-* / --ds-field-padding-y-* |
| radius | component.field.radius | --ds-field-radius |
| focus ring | component.focus-ring.* + component.select.focus-ring.radius.default | --ds-focus-ring-* + --ds-select-focus-ring-radius-default |
| typography | component.field.text.* + component.form-field.{label|helper|required}.* | --ds-field-text-*, --ds-form-field-* |
CSS classes
| Class | Description |
|---|---|
ds-select | Wrapper element for the select |
ds-select__field | The native <select> element |
ds-select__arrow | Custom dropdown arrow indicator |
ds-select__icon | Leading icon inside the wrapper |
ds-select--sm | Small size (32px height) |
ds-select--md | Medium size (40px height, default) |
ds-select--lg | Large size (48px height) |
ds-select--error | Error state with red border |
ds-select--disabled | Disabled state (or use native disabled) |
ds-select--readonly | Readonly visual state (combine with native disabled) |
ds-field__error | Error row with automatic icon and message (shown when ds-field--error is set) |
ds-field__label-row | Horizontal row with label and required asterisk |
ds-field__required | Asterisk * in feedback/error/content/default (decorative, aria-hidden) |
ds-field--no-label | Hides the label row when Show Label = false |
ds-field--no-helper | Hides helper text when Show Helper Text = false |
Accessibility
Keyboard interaction
| Key | Action |
|---|---|
Tab | Moves focus to the select |
Space / Enter | Opens the dropdown (browser-native) |
Arrow Up / Arrow Down | Navigates options |
Escape | Closes dropdown |
| Any letter | Jumps to first option starting with that letter |
All keyboard interactions are handled natively by the <select> element. No custom JavaScript is required. Disabled selects are removed from tab order automatically.
Accessibility
| WCAG criterion | Requirement | Status |
|---|---|---|
| 2.4.11 Focus Appearance (AA) | Focus ring 2px + 2px gap, contrast >= 3:1 against adjacent colors | ✓ |
| 2.5.8 Target Size min (AA) | Smallest size (32px) exceeds 24px minimum | ✓ |
| 2.5.5 Target Size (AAA) | Large (48px) meets 44px touch target | ✓ |
| 1.4.3 Contrast (AA) | Text and arrow contrast >= 4.5:1 against background. Disabled exempt. | ✓ |
| 4.1.2 Name, Role, Value (A) | Use native <select> element. Associate with <label> via for/id. | ✓ |
| 1.3.1 Info and Relationships (A) | Error states use aria-invalid="true" and aria-describedby. | ✓ |
| 1.4.1 Use of Color (A) | Error state has visible red border + text message, not just color. | ✓ |
.ds-select__arrow) uses pointer-events: none -- it is purely decorative and does not interfere with click or keyboard interaction.Native
<select> has no readonly attribute. The DS uses disabled combined with the ds-select--readonly class for visual differentiation. Unlike disabled, readonly should visually communicate that the value is set but not editable.
Required = true, add aria-required="true" to the <select> — the visual asterisk (.ds-field__required) is decorative (aria-hidden="true"). When Show Label = false, use aria-label on the <select>. Helper Text should be linked via aria-describedby.
Output responsibility
Preserve native element semantics, accessible names, keyboard behavior, and visible focus.
Validation evidence
Covered by the stable Storybook, public runtime tests when applicable, keyboard scenarios, responsive checks, and Axe.