components / select

Select

Field-shell trigger and single-select listbox built on DK overlays and list navigation.

Overview

What it is

This section explains the intent of the component before the implementation details.

  • Select is the simpler single-choice listbox built on the DK field shell and overlay primitives.
  • Use it when the options are moderate in number and filtering is unnecessary.

Aliases

dropdown

Decision guide

How to choose it

Use these notes to decide quickly whether this is the right DK component for the job.

Decision guide

  • Choose Select when the interaction should feel deliberate, documented, and reusable across product surfaces.
  • If the scenario is more specialized than the current Select contract, prefer a simpler component or build a dedicated workflow on top of the lower-level DK primitives.

Do

  • Keep the Select label, value, or status language direct enough to scan quickly.
  • Use the documented size and state recipes instead of inventing one-off variants in product code.

Do not

  • Do not hide the main purpose of Select behind decorative copy or overloaded surface treatments.
  • Do not stretch Select into a workflow it was not designed to handle just because the visuals are close.

Usage

When to use

Prefer these situations when choosing this component.

  • Use Select for finite single-choice option sets where the user can comfortably scan the list.
  • Use placeholder, description, and error states the same way you would for other field-family components.

Usage

When not to use

These patterns are better served by a different component or a simpler surface.

  • Do not use Select when the list is long enough to require filtering; use Combobox.
  • Do not use Select for multi-select in this v1 contract.

Anti-patterns

  • Using Select as a generic layout container instead of a purpose-built interaction or content surface.
  • Forking the documented recipe in product code instead of extending the shared DK contract.

Migration notes

  • Migrate legacy select usage by mapping existing variants and states onto the documented DK props before porting any custom styling.
  • Treat the docs examples as the reference contract for accessibility and event behavior during adoption.

Anatomy

Structural parts

The anatomy explains which pieces matter to the recipe and accessibility model.

field wrapper

root

Provides the label, helper text, and validation shell.

field button

trigger

Opens the listbox and reflects the current value.

options surface

listbox

Anchored list of available options.

selectable row

option

Single option row with label and optional description.

API

Public interface

The docs contract distinguishes props, DOM events, and slots so integration behavior is explicit.

Props

NameTypeDefaultDescription
valuestringControlled selected value.
itemsArray<{ value: string; label: string; description?: string; disabled?: boolean }>Available options.
labelstringVisible field label.
descriptionstringSupporting helper copy.
errorstringValidation message.
placeholderstringText shown before a selection is made.
requiredbooleanfalseMarks the field as required.
disabledbooleanfalseDisables the field.
namestringNative form field name.
idstringOptional field id.
size'sm' | 'md' | 'lg''md'Chooses the size recipe.
onChange(detail: { value: string | undefined }) => voidCallback fired when a value is selected.
themeThemeContractOverrides the compiled DK theme used to resolve tokens and recipes for this component.

Events

NamePayloadDescription
change{ value: string | undefined }Fires when the component commits a new value.

Slots

NameDescription
defaultSelect is data-driven in v1 and does not expose custom option slots.

Recipes

Variants, sizes, and states

These notes summarize the intended recipe surface rather than exposing raw implementation detail first.

Variants

  • default: Select uses one single-choice field shell and varies by size.

Sizes

  • sm: Compact select
  • md: Default select
  • lg: Larger select

States

  • closed: Listbox hidden
  • open: Listbox visible
  • selected: Value selected
  • invalid: Validation state

Accessibility

Accessibility contract

This is the behavior the component promises to assistive tech and keyboard users today.

Semantics

  • Uses a button-trigger plus listbox relationship to expose the selectable options.

Keyboard

  • Arrow keys move through options when the listbox is open and Enter commits the active option.

Screen readers

  • Selected value, listbox state, and option movement remain explicit.

Checklist

  • Verify the visible label or title still produces a clear accessible name.
  • Verify focus remains obvious in every supported state and size.
  • Verify disabled, selected, and error states do not rely on color alone.

Implementation

Tokens and slot vars

This section shows the representative compiled slot variables that the runtime consumes for the selected design system.

root

--dk-field-stack-gap
0.45rem
--dk-motion-duration
200ms
--dk-select-offset
8px

label

--dk-field-label-color
#16181c
--dk-field-label-size
clamp(1.333rem, 1.2222rem + 0.494vw, 1.667rem)
--dk-field-label-weight
600

trigger

--dk-select-trigger-bg
#f2f5fb
--dk-select-trigger-fg
#16181c
--dk-select-trigger-border
#95989d
--dk-select-trigger-block-size
48px
--dk-select-trigger-inline-padding
clamp(1.333rem, 1.2222rem + 0.494vw, 1.667rem)
--dk-select-trigger-radius
clamp(0.75rem, 0.6875rem + 0.278vw, 0.938rem)
--dk-select-trigger-font-size
clamp(1.333rem, 1.2222rem + 0.494vw, 1.667rem)

icon

--dk-select-icon-size
1rem
--dk-select-icon-color
#16181c

description

--dk-field-description-color
#16181c
--dk-field-description-size
clamp(0.75rem, 0.6875rem + 0.278vw, 0.938rem)

error

--dk-field-error-color
#cc272e
--dk-field-error-size
clamp(0.75rem, 0.6875rem + 0.278vw, 0.938rem)

surface

--dk-select-surface-bg
#f2f5fb
--dk-select-surface-fg
#16181c
--dk-select-surface-border
#95989d
--dk-select-surface-radius
clamp(1.333rem, 1.2222rem + 0.494vw, 1.667rem)
--dk-select-surface-shadow
0 10px 15px rgba(0, 0, 0, 0.06), 0 4px 6px rgba(0, 0, 0, 0.04)
--dk-select-surface-width
300px
--dk-select-surface-padding
clamp(0.75rem, 0.6875rem + 0.278vw, 0.938rem)

item

--dk-select-item-bg
transparent
--dk-select-item-fg
#16181c
--dk-select-item-radius
clamp(0.75rem, 0.6875rem + 0.278vw, 0.938rem)
--dk-select-item-min-height
44px
--dk-select-item-inline-padding
clamp(1.333rem, 1.2222rem + 0.494vw, 1.667rem)

itemLabel

--dk-select-item-label-size
clamp(1.333rem, 1.2222rem + 0.494vw, 1.667rem)
--dk-select-item-label-weight
550

itemDescription

--dk-select-item-description-size
clamp(0.75rem, 0.6875rem + 0.278vw, 0.938rem)

Implementation notes

  • Select is intentionally single-select and non-searchable in v1 so the interaction model stays predictable.

Implementation checklist

  • Use the public component API first and only drop to lower-level primitives when the component contract genuinely does not fit.
  • Keep theme overrides token-driven so proofs and recipes stay meaningful.
  • Verify the shipped examples and proof fixtures still represent the real product scenario you are implementing.

Examples

Copy-paste examples

Each example is intentionally practical, grouped by starter, common pattern, and edge-case coverage.

Examples
3
Depth
expanded

Starter

1 example

starter

Starter: environment selector

A standard single-select field.

Copy snippet

<Select label="Environment" description="Choose the deployment target." placeholder="Select an environment" items={items} />

Common patterns

1 example

common

Common: required selection

Use the error prop when the selection is mandatory.

Copy snippet

<Select label="Environment" error="Please choose an environment." placeholder="Select an environment" items={items} />

Edge cases

1 example

edge

Edge: externally controlled listbox

Control the selected value when surrounding state owns the workflow.

Copy snippet

<Select label="Environment" items={items} value={selectedEnvironment} onChange={(detail) => selectedEnvironment = detail.value} />

Verification

Curated proof fixtures

Proofs stay visible in the docs so the system shows what it can guarantee, not just what it can render.

default (size=sm)

pass

size=sm

  • Contrast: 98.5 Lc
  • Target: 44px
  • Rows: 44px
  • Surface: 300x280

default (size=md)

pass

size=md

  • Contrast: 98.5 Lc
  • Target: 48px
  • Rows: 44px
  • Surface: 300x280

default (size=lg)

pass

size=lg

  • Contrast: 98.5 Lc
  • Target: 52px
  • Rows: 44px
  • Surface: 300x280

selected (size=sm)

pass

size=sm

  • Contrast: 98.5 Lc
  • Target: 44px
  • Rows: 44px
  • Surface: 300x280

selected (size=md)

pass

size=md

  • Contrast: 98.5 Lc
  • Target: 48px
  • Rows: 44px
  • Surface: 300x280

selected (size=lg)

pass

size=lg

  • Contrast: 98.5 Lc
  • Target: 52px
  • Rows: 44px
  • Surface: 300x280

invalid (size=sm)

pass

size=sm

  • Contrast: 98.5 Lc
  • Target: 44px
  • Rows: 44px
  • Surface: 300x280

invalid (size=md)

pass

size=md

  • Contrast: 98.5 Lc
  • Target: 48px
  • Rows: 44px
  • Surface: 300x280

invalid (size=lg)

pass

size=lg

  • Contrast: 98.5 Lc
  • Target: 52px
  • Rows: 44px
  • Surface: 300x280