Design Language
How UDS components look in each state. Every preview on this page is a real component — the colors come from the same tokens as production. Where components disagree on the same state, both are shown so we can pick one.
Interaction states
Four states an interactive element moves through: at rest, pointer-over, pressed, and keyboard-focused. Each preview below is a real UDS component rendered in that state.
Default
restHover
pointer over→ Hover over a real button on the Button page to see it animated.
inconsistency Four different form-field hover patterns coexist today — see §12.2.
Focus
keyboard focus→ Tab through any real field on the Text Input page to see it.
Three constructions exist — outline+offset, stacked box-shadow, single-ring. See §6 deep dive.
Pressed
:active→ Mouse-down on a real button to see this state.
Validation states
How fields, notifications, and badges communicate success, warning, error, and info.
Error
red, boldInfo
blueSuccess
greenWarning
orangeAvailability states
Disabled means “you can’t act here right now.” Read-only means “this value is what it is.” They look different on purpose.
Disabled
non-interactiveRead-only
value is fixedUser can read + copy the value but not edit. proposed
Loading
deferredNot specified in UDS today. Components that need a loading look currently fall back to Disabled as the closest approximation.
deferred Waiting on designer review.
Selection states
How “this is the chosen one” reads visually. Filled elements (chip, toggle, checkbox) go more saturated. Outlined elements (tile, tab, radio) gain a colored marker and recolor text to text-interactive.
Checkbox · checked
filledRadio · selected
dot fills + label tintsChip · selected
filled, inverse textDisabled vs Read-only — quick reference
Confusing these two is the most common spec-writing mistake. Use this table.
| Question | Disabled | Read-only |
|---|---|---|
| User can read the value? | technically yes, but greyed | yes, full primary text |
| User can copy the value? | usually not (cursor not-allowed) | yes |
| User can focus the element? | no (skipped by Tab) | yes |
| User can edit? | no | no |
| Communicates “temporarily unavailable”? | yes | no — “this is fixed information” |
| Communicates “you can’t change this”? | yes | yes (because the system owns it) |
Focus ring construction
UDS has three focus-ring constructions in the current CSS. They’re not interchangeable — pick one by the rules below, not by aesthetic preference.
A. outline + outline-offset recommended
Outline with a transparent offset. The gap is genuinely empty space — always blends with whatever’s behind. Default for new code.
Used by: checkbox, radio, link, toggle, tabs, text-area, dropdown items, dialog close, notification close.
B. Stacked box-shadow
Two box-shadows: a surface-colored “gap” layer + the focus ring outside it. Use when an outline would be clipped (form field with overflow:hidden). Theme-safe ONLY when the gap binds to surface-main, not surface-white.
Used by: chip, search (theme-safe). Bug: button, text-input, dropdown bind the gap to surface-white — visible white ring in dark mode. See §12.1.
C. Single ring, no gap
A direct ring on the element with no separating gap. Reserved for large containers where extra visual weight competes with neighbors.
Used by: tile only.
Decision tree
Universal rules
- Ring color: always border-outline-focus-visible
- Ring thickness: 2 px for inline controls and form fields, 3 px for tiles
- Gap (in A or B): 1–2 px
- Selector:
:focus-visible, never:focus— the latter fires on every click - Error doesn’t recolor the focus ring — it stays blue (search is the only exception today)
Surface treatments
Page surfaces, content surfaces, interactive surfaces, status surfaces. Full chip set with resolved colors. For the full token reference see Semantic Colors.
Content surfaces
Interactive surfaces
Status surfaces
surface-white and surface-black are literal white and black in every theme. Almost never the right token — usually you want surface-main. Three components in UDS use surface-white incorrectly for focus-ring gaps; see §12.1.Elevation / shadow scale
Three depth levels plus a bento variant for dashboards. Pick by purpose, not by size. Live preview of each shadow below.
| Token | Use when… | Live examples |
|---|---|---|
shadow-depth-100 | A small element is lifted off its surface | Toggle thumb |
shadow-depth-300 | A transient overlay near its trigger | Tooltips, dropdown menus, popovers |
shadow-depth-500 | A modal that floats above the entire page | Dialogs, sheets |
shadow-bento | Bento-style card grouping (dashboards) | Dashboard cards |
Border radius scale
Two families: input for things you interact with, container for surfaces holding content.
| Token | Live examples in UDS |
|---|---|
border-radius-input | text-input, dropdown trigger, button, dialog close, tile, dropdown items |
border-radius-container-sm | notification, badge, link focus, tab focus |
border-radius-container | tooltip, chip |
border-radius-container-xl | dialog (24 px) |
border-radius-container-full | toggle control + thumb |
Density / sizing
Two density tracks: default (comfortable) and small (filter rows, dense forms). Side-by-side live preview.
Default (comfortable)
Button: padding: space-150 space-200 (12 × 16 px)
Tab: padding: space-200 space-300 (16 × 24 px)
Small (size="sm")
Button: padding: space-075 space-150 (6 × 12 px)
Tab: padding-top/bottom: space-100 (8 px)
lg size in UDS yet. Components that need a bigger size should check with the designer before adding one.Checked vs Selected vs Active — naming guidance
Three words that get confused. Use the right one for the right element.
| Term | Used by | Pair with |
|---|---|---|
| Checked | checkbox, radio, toggle | <input checked> / aria-checked="true" |
| Selected | tabs, dropdown items, chip, tile | aria-selected="true" / data-selected="true" |
| Active (current) | link, button ([data-selected]) | aria-current="page" |
| Active (pressed) | every interactive control while held down | :active (CSS pseudo) |
Known inconsistencies needs review
Five places where components disagree with each other today. Each one needs a quick decision from the designer. Until that happens, new components default to whichever option is the safest across themes.
12.1 Focus ring gap binding
| Component | Gap binding | Theme-safe? |
|---|---|---|
| button | surface-white | no — white in dark mode |
| text-input | surface-white | no |
| dropdown trigger | surface-white | no |
| chip | surface-main | yes |
| search | surface-main | yes |
Suggested fix: bind the gap to surface-main everywhere. Three components need a small CSS update.
12.2 Hover treatment for form fields
| Component | Hover treatment |
|---|---|
| text-input | (no hover state defined) |
| dropdown trigger | bg → interactive-subtle-hover, border → border-interactive |
| search field | bg → surface-subtle |
| text-area | border → border-outline-hover |
Suggested fix: use text-area’s border-outline-hover everywhere. It reads as hover without competing with the focus ring.
12.3 Disabled border treatment
| Component | Disabled border |
|---|---|
| button (primary) | same as bg — invisible border |
| button (secondary), text-input | transparent |
| dropdown, checkbox, radio, tile | border-disabled |
Suggested fix: use border-disabled on outlined elements (form controls) and transparent on filled ones. Both treatments are fine — they say slightly different things about disabled.
12.4 Toggle’s opacity: 0.7 for disabled
Toggle is the only component that drops opacity for disabled. Either everyone else should match it, or toggle should drop the opacity. Designer call.
12.5 Required dot color token
| Component | Required dot color |
|---|---|
| text-input, dropdown, checkbox, radio | icon-error |
| tile | text-error |
Both resolve to the same red. Pick one and use it everywhere.
A few rules that apply across the board
- Stroke alignment is
INSIDEfor form fields so border weight doesn’t affect outer dimensions. - Border weight stays at 1 px through state transitions for most components. Exceptions: tile thickens to 2 px on selection; tabs use 2 px bottom border.
:focus-visibleis the focus selector, never:focus.- Transitions are
120ms easefor color and border,200ms easefor transforms. - Disabled elements need
pointer-events: noneANDcursor: not-allowed— both are required.
Related pages
- Semantic Colors — the full token vocabulary the constructions here resolve to.
- Spacing — the
space-*scale referenced in density patterns. - Text Styles — typography decisions that pair with these visual treatments.
- Cursor Workflows — the same content for AI agents lives in
.cursor/rules/uds-design-language.mdc.