Skip to content

Interaction — Selection

interaction specs/interaction/selection.kmd

How users mark/unmark items as selected — single-select, multi-select, range-select, and the visual + accessibility contract for each. Material parity (`/foundations/interaction/selection`). Used by lists, tables, file pickers, chips, and any UI where selection is a primary action.

When this spec applies

Primary triggers

All triggers

Specification body

Spec — Interaction: Selection

Facet Visual do Koder Design. Material parity: https://m3.material.io/foundations/interaction/selection.

3 selection patterns

PatternCardinalityAffordance
Single1 of NRadio button OR row-tap (mutually exclusive)
Multiple0..NCheckbox OR chip (toggle) OR row-tap+modifier
Rangecontiguous spanClick + Shift+Click; or drag

R1 — Affordance per cardinality

Single-select

  • Radio buttons when N ≤ 5 and all options must be visible
  • Dropdown / select when N ≥ 6 OR options take vertical room
  • Row-tap (no visual control) when the selection IS the navigation (list-detail layout — tapping a row reveals its detail)

Multi-select

  • Checkbox when each item is identified by a row in a list
  • Chip (filter style) when options are short labels, often spatial (e.g., date ranges, tags)
  • Row-tap with selection mode — long-press enters selection mode; subsequent taps add/remove. Mobile pattern from Gmail/Files.

Range-select

  • Click first item, Shift+Click last — desktop pattern
  • Drag across items — touch and desktop both
  • Date range picker — specialized component for time
  • Always combinable with multi-select (Ctrl+Click toggles individual)

R2 — Visual state per selected item

StateMarker
UnselectedDefault surface; no visual marker
SelectedAccent-tinted background (low opacity) + checkmark/radio dot + bolder border
Hover (selectable, unselected)Surface-variant background; cursor pointer/finger
Hover (selected)Selected style + slightly elevated tint
Focused (keyboard)2px focus ring per states.kmd
DisabledReduced opacity; no hover/focus response

Selected background opacity recipe: color-mix(in srgb, var(--accent) 12%, transparent) (or theme-equivalent)

R3 — Accessibility

  • Selection controls (radio/checkbox) must have:
    • role="radio" / role="checkbox" if not native <input> (prefer native)
    • aria-checked ("true"/"false"/"mixed")
    • aria-label or visible label
  • Row-tap selection must announce state change to screen readers via live region: "Selected. 3 of 12 items."
  • Keyboard: Space toggles, arrows navigate (in list/grid), Shift+arrow extends range, Ctrl+A selects all (if applicable)

R4 — Bulk action affordance

When multi-select is active:

  • A bulk action bar appears (top of list or floating) with:
    • Count: "3 selected"
    • Primary actions: Delete, Archive, Tag (per surface)
    • Cancel / Deselect all
  • Bar uses selected color scheme — accent-tinted, not error-tinted, even for destructive actions (preserve destructive semantics inside per-button styling)
  • Bar is reachable by keyboard (Tab from any selected row)

R5 — Persistence

Selection state is ephemeral by default — refreshing the page clears.

Exception: when the user navigates within a single workflow (e.g., list → detail → back), restore the previously selected item. Use URL fragment or session storage; clear on workflow exit.

R6 — Edge cases

  • Empty list with selection mode — exit selection mode automatically
  • All items selected — show "Deselect all" instead of "Select all"
  • Mixed state (some descendants selected in a tree) — checkbox shows aria-checked="mixed" and indeterminate visual
  • Selection during async load — preserve user intent; if items rearrange, keep selection by ID not by index
  • interaction/states.kmd — focus/hover/pressed/disabled details
  • foundations/elements.kmd — selection is a control affordance
  • themes/color-schemes.kmd — accent-tinted background token

References