Components
ButtonPrimary
When to use
- The single most important action on a view (Save, Submit, Continue).
- When the user's expected next step should be visually unmistakable.
When not to use
- Multiple competing actions — only one primary per region; downgrade the rest to
ButtonSecondary,ButtonOutlined, orButtonText. - Destructive actions in a flow alongside non-destructive primaries — pair a destructive secondary with a non-destructive primary instead.
ButtonSecondary
When to use
- Paired with a
ButtonPrimaryas the alternate path (Cancel, Skip, Discard). - For frequent actions that don't deserve the visual weight of a primary.
When not to use
- As the only action on a view — promote it to
ButtonPrimaryinstead. - For destructive actions — pair a danger-coloured variant with a clear confirmation step.
ButtonOutlined
When to use
- On busy or coloured surfaces (cards, banners, hero sections) where a filled button would clash.
- For tertiary actions in dense toolbars, alongside primary + secondary.
When not to use
- Across plain backgrounds where the border is the only signal it's a button —
ButtonTextreads as a button via colour alone and may be lighter-weight.
ButtonText
When to use
- Inline within text or table cells where a bordered button would feel heavy.
- For "more" / "less" / "show details" affordances next to content.
When not to use
- As the only call-to-action on a view — users may miss it. Use
ButtonPrimaryfor the principal task. - When the underlying action is irreversible — give it visible chrome (outlined or filled) plus a confirmation step.
SplitButton
When to use
- One dominant action with a tight cluster of close variants — Save / Save as draft / Save and continue, Send / Schedule / Send later.
- Toolbar actions where surfacing the most-used variant saves a click and the rest stay one chevron away.
When not to use
- Many unrelated actions — use a
Menuoff aButtonOutlinedtrigger; a SplitButton's primary face implies the menu items are siblings. - Destructive primary alongside benign menu items — the chevron makes the danger one mis-click away; separate them.
Key UX patterns
- The two halves are distinct hit targets: clicking the label runs the default action; clicking the chevron opens the menu — never one combined click that does both.
- Show the most-used or last-used action on the primary face when it makes sense; clearly label it so users know what Enter will do.
- Closing the menu after selection mirrors a normal button — the chosen action runs and focus returns to the trigger.
Accessibility
- Two adjacent buttons inside one wrapper — each needs its own accessible name; the chevron's name is "More action options", not just "menu".
- The menu follows the W3C menu pattern: Arrow keys move between items, Enter activates, Esc closes and returns focus to the chevron trigger.
- Programmatically link the menu to its trigger via
aria-haspopup="menu"andaria-expanded; without it the chevron just looks like a decorative wedge.
SpeedDial
When to use
- Mobile or kiosk surfaces with a single dominant create / compose action plus a couple of close cousins (new note / new audio note / new photo).
- Map / canvas-style apps where a fixed corner FAB is a familiar pattern (Material Design) for "do something here".
When not to use
- Desktop dashboards with room for a real toolbar — a floating dial covers content the user might be reading.
- More than ~5 actions or actions that aren't variants of the same intent — use a
Menuor full-page navigation; users tire of chasing radial menus.
Key UX patterns
- The trigger toggles open / closed; clicking outside or pressing Esc collapses the dial without firing an action.
- Animate the fan-out fast (≤200ms) and provide a clear closed-state icon morph; static reveal looks like a layout glitch.
- Each fan-out item carries its own icon plus a tooltip / label — symbols alone don't survive a first-time encounter.
Accessibility
- The trigger needs
aria-haspopupandaria-expandedreflecting open state; each radial item is a focusable button with its own accessible name. - Tab order moves through the items in their visual order while open; Esc closes and returns focus to the trigger (W3C disclosure / menu pattern).
- Honour
prefers-reduced-motion— a long arc animation is nausea-inducing for some users; collapse to an instant reveal in that mode.
InputText
When to use
- Short, single-line free text (name, email, search query, URL).
- Any entry that comfortably fits on one line — including modest fixed-format text where a mask isn't worth the friction.
When not to use
- Free-form prose or anything the user might type more than a sentence of — use
Textarea. - Strictly structured input: pick
InputNumberfor numbers,InputMaskfor fixed formats,Calendarfor dates. - Binary or categorical choice — use
Checkbox,RadioButton, orDropdowninstead of free-text shorthand.
Key UX patterns
- Always paired with a visible
<label>— the placeholder is a hint, not a label, and disappears on focus. - Validation feedback rendered next to (not over) the field; the field flips to its
isInvalidstate and an adjacent message explains why. - Helper text — when present — sits below the field with the same
aria-describedbyassociation as the error message.
Accessibility
- Programmatic
<label for>association is required; anaria-labelonly suffices when a visible label genuinely cannot be shown. - Set
aria-invalid="true"on error and reference the message viaaria-describedby. - Honour the OS auto-fill / autocomplete contract — set the right
autocompletetoken so password managers and form-fill agents work.
Textarea
When to use
- Free-form prose — comments, descriptions, feedback, notes — where users will plausibly type more than a sentence.
- Any entry that benefits from preserving line breaks (addresses, code snippets without syntax, lyrics).
When not to use
- Short single-line text — use
InputText; a textarea sized down to one row reads as a styling glitch. - Rich formatting (bold, lists, links) — that's an
Editorcase, not a textarea.
Key UX patterns
- Auto-grow as the user types is preferable to a fixed height with an inner scrollbar — the latter hides earlier content from review.
- When a character limit applies, render a live counter outside the field; flip to the
isInvalidstate once the limit is exceeded. - Cmd/Ctrl + Enter as a "submit" shortcut is expected by power users in chat-like contexts; document it via tooltip when the surrounding form supports it.
Accessibility
- Same label /
aria-invalid/aria-describedbycontract asInputText. - Default to a height that fits the expected entry — three or four rows for comments, more for long-form prose. A one-row default reads as "this is a single-line input".
- If you cap text length, the limit must be announced to assistive tech; rely on
aria-describedbypointing at the live counter, not on the visual cue alone.
InputNumber
When to use
- Free numeric entry where a value range, decimals, or units matter (quantity, price, age, weight).
- Currency / percentage / measurement fields where the user benefits from locale-aware grouping (
1,234.50vs1.234,50) or a unit suffix. - Dense tabular edits where steppers (+/−) speed up incremental adjustment more than typing does.
When not to use
- Bounded ranges where the absolute number is less interesting than the position in the range — use
Slider. - Identifiers that happen to be digits (phone, ZIP, OTP, credit card) — use
InputMask,InputOtp, or a plainInputTextwith aninputmodehint; arithmetic semantics don't apply. - Categorical pick-from-set choices that are numbered — use
RadioButtonorSelect.
Key UX patterns
- Set
min/max/stepso increment buttons and Arrow keys round to legal values; clamp invalid input on blur rather than blocking keystrokes. - Reserve steppers for cases where the typical adjustment is a single increment — large jumps are faster typed; tiny mobile steppers frustrate.
- Render the unit (€, %, kg) as a static suffix or use
InputGroup's addon — never as placeholder text the user has to retype around.
Accessibility
- Set
inputmode="decimal"(or"numeric"for integers) so mobile devices show the right keypad without overriding desktop typing. - Stepper buttons need accessible names ("Increase quantity", "Decrease quantity") — an icon alone is invisible to screen readers.
- Honour the locale: parse and display via
Intl.NumberFormatrather than a hand-rolled regex; users innl-NLtype0,5and expect it to mean one half.
InputMask
When to use
- Strictly structured strings with a stable shape (phone number, postal code, IBAN, ISBN, license plate).
- Anywhere a typo in a single position invalidates the whole value and you want to surface that as the user types, not on submit.
When not to use
- Numbers that participate in arithmetic — use
InputNumber. - Dates — use
Calendar; a date mask blocks the user from typing"today","+3", or other natural shorthands the calendar can resolve. - Free text where the format hint is helpful but not enforceable (search, names, descriptions) — use
InputTextwith aplaceholder. - Internationalised formats where the mask varies by country — branch the mask off the country field instead of forcing one global shape.
Key UX patterns
- Render the mask's literal characters (dashes, parens, slashes) as you go; the user types only the variable positions.
- Allow paste of an unmasked value — strip non-mask characters and refit instead of rejecting the whole paste.
- Treat the mask as a soft guide: validate the final value on blur and surface a clear error for partial entries; don't trap focus.
Accessibility
- Same label /
aria-invalid/aria-describedbycontract asInputText; describe the expected format in the helper text or label, not only via the mask. - Set
inputmodeto match the dominant character class ("numeric"for phone / OTP,"text"otherwise) so mobile shows the right keypad. - Don't disable Backspace through the mask literals — the user expects a single press to delete one logical step, not to skip over read-only characters silently.
InputOtp
When to use
- Short verification codes (4–8 characters) sent out-of-band: SMS / email OTP, two-factor codes, magic-link confirmations.
- Any flow where the user is copy-pasting from a separate channel and the discreet boxes signal "paste your code here" more clearly than a single field would.
When not to use
- Long secrets, recovery codes, or anything the user might mistype if they can't see it as one continuous string — use
InputText. - Variable-length codes — the per-digit boxes lock you into a fixed length.
- TOTP authenticator codes the user types from another device — that's still 6 digits, but a single masked field is often less fiddly than six tiny boxes on mobile.
Key UX patterns
- Auto-advance to the next box on each character; Backspace on an empty box jumps to the previous one and deletes there.
- Accept paste at any box — distribute the pasted characters across the remaining boxes from that point, not just into the focused one.
- Trigger automatic submission once the last box fills, or surface an immediate error if the code is rejected; the user shouldn't hunt for a Submit button after typing six digits.
Accessibility
- Set
inputmode="numeric"andautocomplete="one-time-code"so iOS / Android can prefill from SMS and surface the right keypad. - The whole control is a single labelled group — bind one
aria-label("One-time password") to the wrapper and treat each box as an unlabelled cell. Six labels of "Digit 1 of 6" make screen-reader output noisy. - Errors apply to the whole code, not per box: announce a single descriptive error ("Code is incorrect — 3 attempts remaining"), don't paint each box red individually.
InputChips
When to use
- Open-ended multi-value entry where the values aren't drawn from a fixed list (tags, keywords, recipient emails, search filters typed by the user).
- "To:" / "Cc:" recipient fields where pasted comma-separated lists should expand into individually removable chips.
When not to use
- Multi-select from a known list — use
MultiSelectorAutoCompletewith multiple selection so the user picks instead of types. - Single-value entry — chips imply "you can add more"; use
InputTextwhen one is the answer. - Free prose with optional commas — chip-on-comma will cut sentences in half.
Key UX patterns
- Convert to a chip on Enter, Tab, or comma; on paste, split a delimited string into chips at once.
- Backspace on an empty input deletes the previous chip; clicking the chip's close icon removes only that chip and keeps focus in the field.
- Validate per-chip (email shape, max length, dedup) and either reject the entry with an inline message or render the chip in an invalid state — don't accept silently and lose the user's intent.
Accessibility
- Each chip's remove control needs an accessible name ("Remove tag foo"); an icon-only × button reads as "button" to a screen reader.
- Announce additions and removals via an
aria-live="polite"region attached to the wrapper so a non-visual user knows their entry committed. - Tab moves into and out of the whole control as one stop; Arrow keys can navigate between chips so keyboard users don't have to land on each remove button via Tab.
Tag 1Tag 2
InputGroup
When to use
- Currency, unit, or protocol addons that aren't part of the value but clarify it (
$…USD,https://….com,kg). - Inline trigger or action attached to the field's value (a clear button, a copy button, a domain picker).
- Combining a small static dropdown with a free-text field — country code + phone number being the canonical example.
When not to use
- The addon is actually editable data — promote it to its own labelled field.
- Decoration without semantic value — a magnifier icon next to a search field belongs inside the input, not as an addon, otherwise the click target shifts off the visible glass.
- Multi-input layouts where each segment is independent — use a normal field row; an InputGroup signals "these belong to one value".
Key UX patterns
- Addons inherit the input's height and border so the assembled control reads as one element; don't break the seam with shadows or radii.
- Static prefixes are not focusable; interactive addons (buttons, dropdowns) are reachable via Tab in reading order.
- Validation styling applies to the whole group — a red border on just the input with a quiet addon next to it looks like a rendering bug.
Accessibility
- The label points at the actual input, not the wrapper —
<label for>targets the inner<input>. - Addons that carry meaning (currency code, unit) must be in the accessible description: bind them via
aria-describedbyso they're announced once, not silently styled. - Interactive addons need their own accessible name ("Clear search", "Copy URL") — currentColor icon alone reads as "button".
$USD
Checkbox
When to use
- An independent boolean choice (Remember me, I agree to the terms, Subscribe to newsletter).
- Selecting any number from a fixed list — one checkbox per option, all visible at once.
- Surfacing parent / child selection state with the indeterminate variant (some children selected, not all).
When not to use
- Mutually exclusive choice from a small set — use
RadioButton. - An on / off setting that takes effect immediately without a Save action — use
InputSwitchorToggleSwitch; they read as switches, not pending form values. - Many options where users need search or virtualisation — use
MultiSelectorListBox.
Key UX patterns
- Always pair with a visible label — clicking the label toggles the checkbox (the hit target should include the label, not just the box).
- Pre-select cautiously: pre-checking opt-in boxes for marketing or data sharing is non-consensual UX and runs afoul of GDPR Art. 7.
- The indeterminate state is set programmatically (
el.indeterminate = true) — there's no HTML attribute for it.
Accessibility
- Native
<input type="checkbox">beats a custom one — the platform handles focus, state announcement, and Space-to-toggle for free. - If the checkbox is rendered with custom visuals, expose
aria-checked(including"mixed"for indeterminate) and ensure Space activates it. - Group related checkboxes inside
<fieldset>with a<legend>so screen readers announce the group's purpose before the options.
RadioButton
When to use
- Two to roughly five mutually exclusive options where seeing every option at once helps the user decide (shipping speed, payment method, sort order).
- When the answer space is small and stable enough that a dropdown would feel like unnecessary clicks.
When not to use
- More than ~5 options or a long tail of values — use
Dropdown/Selectso the list scrolls instead of dominating the form. - Binary on / off — use
InputSwitch; a two-radio group reads heavier than a switch. - Multi-select (any number selectable) — use
Checkbox.
Key UX patterns
- Stack vertically by default — horizontal alignment is harder to scan and tends to cramp labels.
- Pre-select the safest / most common option when a default genuinely exists; otherwise leave the group unselected so users can't submit a non-choice they didn't make.
- Each option's clickable area should include the label, not just the dot.
Accessibility
- The group is a single tab stop: Tab moves into the group; Arrow keys cycle and select; Tab moves out. This is the W3C radio-group pattern — don't break it with custom JS that treats each radio as its own tab stop.
- Wrap the group in
<fieldset>with a<legend>so screen readers announce "Shipping speed, radio group, 1 of 3" before the first option. - Set
aria-checkedon custom-rendered radios; the native input handles this for free.
Select
When to use
- Six or more mutually exclusive options where rendering them all as radios would dominate the form.
- Familiar value sets users won't second-guess (country, timezone, language, status) — the closed dropdown saves vertical space without hurting recognition.
- When the "no selection" state is meaningful and a placeholder ("Choose a country") communicates that better than an empty radio group.
When not to use
- Two to roughly five options — use
RadioButton; a closed dropdown for three values hides choice from the user. - Multi-select — use
MultiSelect. - Long lists where the user would benefit from typing — use
AutoCompleteso they don't scroll a 200-item dropdown. - Binary on/off — use a switch.
Key UX patterns
- Sort options the way users think about them — alphabetic for country, semantic for status, frequency-based for "recent first" — and surface a default that's safe rather than guessing.
- Closed-state shows the selected option, not the label — the field's purpose belongs in its
<label>above it. - Open the panel below the trigger when there's room, flip up otherwise; never force the user to scroll the page to see the panel.
Accessibility
- Follow the W3C combobox / listbox pattern — Enter / Space opens the panel, Arrow keys move the active option, Enter commits, Esc closes.
- Set
aria-expandedon the trigger androle="option"witharia-selectedon each item;aria-activedescendanttracks the highlighted option without moving DOM focus. - Provide first-letter typeahead in long lists — typing
"n"jumps to the first option starting with N — and announce the active option through the listbox's accessibility tree.
Dropdown
When to use
- Inline filter controls and toolbar pickers — sort by, group by, view density — where the trigger sits on a row with sibling controls.
- Compact one-of-many choice in dense layouts (table headers, settings rows) where a stacked
Selectwith its label above would break the row rhythm.
When not to use
- Form-style single-select with a prominent label above the field — use
Select; it carries the same listbox semantics with the canonical labelled pattern. - Multi-select — use
MultiSelect. - Free-text-with-suggestions — use
AutoComplete.
Key UX patterns
- The trigger displays the current value (or placeholder); the panel opens below by default and flips up if it would clip.
- Inline use often skips the visible label — when this is the case, ensure the placeholder reads as a category ("Sort by") not as a value.
Accessibility
- Same W3C combobox / listbox pattern as
Select:aria-expanded, Arrow-key navigation, Esc to close, type-ahead first-letter jump. - An
aria-labelon the trigger is mandatory when no visible label is rendered — "Sort by, current value Newest" beats an unlabelled chevron.
MultiSelect
When to use
- Picking any number of values from a known list where rendering every option as a checkbox row would crowd the form (audiences, tags, table-column toggles, dietary restrictions).
- Filter chips on listing pages — multi-tag filters that compose into a single query.
- When users need search inside the candidate set as well — the panel can host a search field while the trigger stays compact.
When not to use
- Single-select — use
Select; multi-select with one allowed value is a UX trap (users wonder if they can add another). - Free-form values that aren't in the list — use
InputChipsorAutoCompletewith a "create new" branch. - Few options that always fit on screen — use a column of
Checkboxrows; opening a panel to toggle three boxes is unnecessary work.
Key UX patterns
- Show selected values as truncatable chips inside the trigger; cap at "+N more" once the count overflows the trigger's width.
- Offer "Select all" / "Clear" inside the panel for lists longer than a screen — and surface the count of selected items in the trigger.
- Sort and group long lists; sticky group headings inside the panel help users navigate without losing their place.
Accessibility
- Each option exposes
aria-selected(or a checkbox role) so AT users hear "selected" / "not selected" while traversing the list. - Selecting / deselecting an option must not close the panel — multi-select means "keep going"; provide an explicit Done / close affordance.
- Announce selection summaries via
aria-live("3 of 12 selected") so non-visual users can audit their progress without scanning the chip strip.
Pick cities
AutoComplete
When to use
- Long lists where scrolling a
Selectis impractical — countries with subdivisions, products in a catalogue, users in an organisation. - Typed search that expects to land on a known entity (assignees, mentions, tags), where the user benefits from disambiguation as they type.
- Async-loaded result sets where suggestions stream in from the server keystroke-by-keystroke.
When not to use
- Free-text search where any string is a valid query — use
InputText; suggestions that the user has to dismiss feel like noise. - Closed lists short enough to fit in a
Select— the typeahead overhead isn't worth it under a dozen items. - Multi-step structured input (date, mask, OTP) — use the dedicated component.
Key UX patterns
- Open the suggestion list as soon as there's something useful to show (typically after 1–2 characters); don't open on focus when there are no suggestions yet.
- Highlight the matching substring inside each suggestion so users can verify why a result is a match.
- Debounce async fetches (~200ms) and surface a quiet loading hint inside the panel; never replace the user's typed text with a placeholder while the request is in flight.
- Distinguish "free text accepted" mode (commit any value on Enter) from "must pick from list" mode (Enter without a selection shows a validation error). Pick one and document it next to the field.
Accessibility
- Implements the W3C combobox + listbox pattern:
aria-controlswires trigger → panel;aria-activedescendantmoves the highlighted option without moving DOM focus. - Announce result counts via
aria-live="polite"("12 results") so screen-reader users know whether typing more would narrow further. - "No results" must be announced — an empty panel that the user can't see equals invisible feedback.
ListBox
When to use
- Picker UIs where keeping the options visible — and selectable without an extra click — is more useful than the compactness of a dropdown (transfer lists, side-by-side selectors, settings panels).
- Multi-select scenarios where users repeatedly toggle items and a closing dropdown would slow them down.
- Long lists where a built-in search field plus a tall scrollable panel beats a dropdown that reflows the page.
When not to use
- Compact forms with vertical-space pressure — use
SelectorDropdown; a tall always-open list dominates. - Boolean choice or 2–5 options — use
CheckboxorRadioButton; ListBox feels heavyweight at small N. - Free-text suggestions — use
AutoComplete.
Key UX patterns
- Allows single or multi-select — surface the mode through the visual treatment (radio dots vs check marks) so users don't guess.
- Pair with a search input above the list when entries exceed roughly a screen height; otherwise rely on first-letter jump.
- Selection persists while the panel is visible; no Save / Apply step needed unless the parent flow demands one.
Accessibility
- Implements the W3C listbox pattern:
role="listbox"withrole="option"children carryingaria-selected;aria-multiselectableon the listbox when multi-select is enabled. - Arrow keys move active option, Space toggles in multi-select, Enter commits in single-select; Home / End jump to first / last.
- Always provide a visible group label or
aria-labelledby— a free-floating list is meaningless out of context.
Amsterdam
Berlin
Paris
SelectButton
When to use
- Two to roughly five mutually exclusive options where a button-shaped target is more affordant than a radio dot (view mode: list / grid / map; chart range: day / week / month / year).
- Toolbar contexts where a horizontal segmented row matches the surrounding control rhythm.
When not to use
- Multi-select — that's
ToggleButtonper option, or just a row ofCheckbox; SelectButton is single-select. - Six or more options — the row gets too wide and labels truncate; use
DropdownorSelect. - Form fields with strong vertical-stack convention (one field per row) — radios read more conventionally there.
Key UX patterns
- The whole row reads as one control: borders join, the active segment is clearly distinguished, hover affordance is consistent across segments.
- Pre-select a sensible default — an unselected SelectButton reads as broken; if no default fits, leave one segment selected with explicit "All" or "Any" semantics.
- Labels stay short (one to two words); icon-only segments need tooltips and accessible names.
Accessibility
- Use the W3C radio-group pattern under the hood —
role="radiogroup"on the wrapper,role="radio"witharia-checkedper segment; one tab stop, Arrow keys cycle. - The group needs an accessible name via
aria-labeloraria-labelledby— "View mode" before the segment names. - Don't render with
role="button"per segment — that breaks the single-selection semantics; users hear "button, button, button" and never "1 of 3 selected".
ToggleButton
When to use
- Subscribe / Subscribed, Follow / Following, Pin / Pinned — single binary states where the action verb itself communicates state more clearly than a switch graphic.
- Toolbar formatting toggles where pressed-state styling (bold, italic) is the standard convention.
When not to use
- Settings rows where on/off is a property, not an action — use
InputSwitchorToggleSwitch; switches read as state, ToggleButton reads as "click me to do something". - Form values that submit later — use
Checkbox; a ToggleButton implies the change took effect on click. - Single-select from a small set — use
SelectButton; one ToggleButton in isolation is binary, several next to each other read as multi-select.
Key UX patterns
- The label and / or icon changes between off and on states — passive colour-only toggles are easy to miss.
- Pressed-state uses a clear visual distinction (filled vs outlined, or inverse colours) — not just a 5% background tint.
- Optimistic state-flip is fine for low-stakes toggles; for slow or failable actions, show a transient pending state and roll back with an inline message on failure.
Accessibility
- Use
aria-pressed="true|false"on a real<button>— screen readers announce "pressed" / "not pressed", which matches the visual semantic exactly. - Don't combine
aria-pressedwithrole="switch"— pick one model and stick to it; W3C ARIA Authoring Practices treats them as alternatives. - Space and Enter both activate the button (default
<button>behaviour); ensure focus ring is visible against both pressed and unpressed backgrounds.
ToggleSwitch
When to use
- Settings that take effect immediately — notifications on/off, dark mode, autoplay — without a Save action.
- Single-state preferences where the choice is binary and the user benefits from seeing the result of the toggle right away.
When not to use
- Form fields whose value is committed only on submit — use
Checkbox; a switch implies "this is now on", not "I will save this later". - Mutually exclusive choice across more than two options — use
RadioButtonorSelectButton. - Cases where toggling is destructive or hard to reverse (deactivate account, drop a backup) — confirm via
Dialoginstead; a switch is too easy to flip.
Key UX patterns
- The thumb's position is the state — combine with a clear on/off label nearby; colour alone (green = on) is not enough.
- If the change requires a network call, show optimistic visual state and surface failure inline rather than rolling the switch back without explanation.
- Pair with a short helper text when the consequence isn't obvious from the label ("Email notifications" → "We'll email you when …").
Accessibility
- Use
role="switch"witharia-checked="true|false"— screen readers announce "switch, on / off" rather than the generic "checkbox, checked". - Space toggles, Tab moves; both states must be reachable via keyboard with a visible
:focus-visiblering. - Don't rely on a CSS-only toggle without an underlying checkbox / button — assistive tech needs the role and the state.
InputSwitch
When to use
- Settings rows in user preferences where the switch sits next to a label and helper text in the same visual rhythm as
InputText/Select. - When a flow contains a mix of pickers and toggles and you want the toggle to align with form-field column conventions.
When not to use
- Inline contexts (cards, list rows, dashboards) where a more visually self-contained switch reads better — use
ToggleSwitch. - Form values that commit only on submit — use
Checkbox; switches imply immediate effect. - Mutually exclusive choice — use
RadioButtonorSelectButton.
Key UX patterns
- Same immediate-commit semantic as
ToggleSwitch— the value flips on click and persists; surface failures inline rather than silently rolling back. - Differentiate from
ToggleSwitchby labelling and surrounding spacing rather than the switch itself; consumers should be able to swap one for the other without the user noticing.
Accessibility
- Same
role="switch"+aria-checkedcontract asToggleSwitch; Space toggles, Tab moves. - Pair with a programmatic label (
<label for>oraria-labelledby) — an unlabelled switch is meaningless to a screen reader.
Slider
When to use
- Bounded continuous or stepped numeric values where the user cares about the position in the range, not the exact number (volume, brightness, opacity, zoom).
- Quick approximate adjustments where dragging is faster than typing — image editors, rating bars, price ranges.
- Range filters (two-thumb) for "min — max" selection over a value space.
When not to use
- Exact-value entry where users know the number — use
InputNumber; sliders are imprecise on small screens. - Discrete categorical choice — use
SelectButtonorRadioButton. - Wide ranges with too many possible values to feel each step — combine with an
InputNumberfor typed entry.
Key UX patterns
- Show the current value somewhere visible — either as a constant readout or in a tooltip that sticks while dragging — so the user can target a specific number.
- Snap to
stepon coarse adjustments; allow finer steps via Shift+Arrow when the use case demands precision. - Hit area extends well beyond the thumb's visual circle (44×44 minimum on touch); the track itself is also a click target that jumps the thumb.
Accessibility
- Use
role="slider"witharia-valuemin,aria-valuemax,aria-valuenow, andaria-valuetextwhen the displayed value differs from the raw number ("8 of 10" or "low"). - Arrow keys adjust by step; Home / End jump to min / max; PageUp / PageDown move by a larger step. This is the W3C slider pattern — don't replace it with custom drag-only behaviour.
- For two-thumb range sliders, each thumb is its own
role="slider"with its own valuemin/valuemax — they constrain each other but each is independently focusable.
Knob
When to use
- Audio interfaces (volume, gain, pan) where the rotary metaphor matches users' mental model from physical hardware.
- Industrial / IoT dashboards displaying a current value within a bounded range as a gauge — temperature, RPM, fuel.
- Custom configurators where the visual presence of a dial is itself a brand signal.
When not to use
- Standard numeric entry — use
InputNumber; a knob is harder to set to an exact value than a typed field. - Linear ranges where the user thinks left-to-right — use
Slider. - Touch-first interfaces with small targets — fine rotation gestures are slow on phones.
Key UX patterns
- Always render the current value as text — the dial alone hides the exact number.
- Drag in a circular gesture; mouse-wheel scroll over the knob also adjusts (with the page-scroll suppressed when the knob has focus).
- Set
min/max/stepexplicitly; loop-around behaviour rarely matches expectations and surprises users.
Accessibility
- Use
role="slider"witharia-valuemin/aria-valuemax/aria-valuenow— the W3C ARIA pattern doesn't have a dedicated knob role; a slider's semantics are correct here. - Arrow keys adjust by step, Home / End jump to bounds; users without pointer devices can't rotate via gesture, so the keyboard contract is mandatory.
- Provide an alternative typed entry for users who need an exact value — a knob alone is not WCAG 2.5.7 (Dragging Movements) compliant on its own.
40
Rating
When to use
- Soliciting subjective evaluation on a small ordinal scale (product review, feedback after a session, satisfaction with an interaction).
- Displaying aggregate ratings ("4.3 stars from 287 reviews") in read-only mode.
When not to use
- Categorical preference (favourite colour, top three items) — use
Checkbox,RadioButton, or a ranking control. - Continuous-quality measurements — use
Slider; stars imply a discrete ordinal. - NPS or large Likert (1–10) where five stars compresses the resolution users need — use a numeric scale or labelled radio group.
Key UX patterns
- Allow clearing the rating once set — users change their minds; provide a "Clear" affordance or a tap-on-current-value to reset.
- Render filled stars left-to-right with a clear hover preview; half-star steps are useful but make the control twice as fiddly to land.
- Pair with a comment field when the rating is low — most actionable feedback lives in the open text, not the star count.
Accessibility
- Use a radio-group pattern under the hood —
role="radiogroup"on the wrapper,role="radio"witharia-checkedper star — so screen-reader users hear "3 of 5" and can pick via Arrow keys. - Provide an
aria-labelper star describing the value ("3 stars") — a generic "star" label loses the meaning. - Display-only ratings should expose
aria-label="4.3 out of 5 stars"on the wrapper; a row of star icons alone is decorative to AT.
ColorPicker
When to use
- Design-tooling-style apps where users pick arbitrary colours (theme builders, white-label customisation, charting overrides).
- Anywhere users supply a colour value and a typed hex / HSL field would be too fiddly without a visual gradient + eyedropper.
When not to use
- Predefined palettes — render a row of swatches as
RadioButtonorSelectButton; an open colour picker is overkill when the answer must be one of five brand colours. - Brand-restricted apps where any colour is a bug — restrict the surface to a curated palette swatch grid.
Key UX patterns
- Trigger renders the current colour as a filled swatch; clicking opens a popover with a 2D saturation / lightness pad, hue slider, and hex / RGB inputs.
- Live-preview the change as the user drags; commit on close and offer a Cancel that restores the original.
- Integrate the platform's
EyeDropper APIwhere supported — sampling a colour from anywhere on screen is faster than matching by eye.
Accessibility
- Provide a typed hex / RGB / HSL field as a first-class entry path — drag-only colour selection is unusable without sight (and fails WCAG 2.5.7 Dragging Movements).
- Each interactive element inside the popover needs an accessible name ("Hue", "Saturation and lightness", "Hex value"); the 2D pad is a
role="slider"with botharia-valuetextfor X and Y descriptions. - Announce the selected colour as text ("Selected: red, hex #c0392b") so screen-reader users get more than a colour code.
Calendar
When to use
- Any date entry — birthdays, deadlines, appointments, booking date pickers — where the user benefits from seeing weekday alignment, weekends, holidays, or already-booked days.
- Date-range selection (check-in / check-out, billing period) where two clicks on a single grid express the range better than two separate fields.
- Inline calendars on dedicated booking surfaces where the grid is the centrepiece, not a sidecar to a text field.
When not to use
- Birth date or any historical date many decades back — a typed
InputMaskwith year-month-day is faster than scrubbing through 50 months. - Recurring schedule rules ("every Tuesday at 09:00") — pair a calendar with a rrule editor; a calendar alone won't capture the rule.
- Time-only entry — separate time pickers (or a typed
InputMask) avoid the visual baggage of a date grid.
Key UX patterns
- Allow typed entry into the trigger field as a fast path — natural language ("today", "next Friday", "+3 days") is a power-user upgrade where the parser supports it.
- Highlight today and the currently selected date distinctly; weekend / holiday tinting is a useful affordance once it's culturally correct (which weekend?).
- Range selection: first click sets start; hovering shows the prospective range; second click commits. Allow re-selecting either end without first clearing.
- Disable out-of-range days but keep them visible — letting users see why a day is unavailable beats hiding the explanation.
Accessibility
- Implements the W3C date-picker dialog pattern: Arrow keys move within the grid, PageUp / PageDown change month, Shift+PageUp / Shift+PageDown change year, Enter selects, Esc closes.
- Each cell carries an
aria-labelwith the full date ("Wednesday, 15 May 2026") so screen-reader users hear the value, not just "15". - Honour locale: first day of week, month names, and date format must follow the user's locale, not a hard-coded en-US default.
- The trigger input is its own labelled field; date pickers must remain usable when the popover fails to load — typed entry is the fallback contract.
FileUpload
When to use
- Forms requiring document, image, or media upload (CV submission, avatar upload, evidence in a support request).
- Bulk-import flows where users drop a folder's worth of files at once.
- Asset uploaders inside an editor surface where uploaded files populate a media library or attachment list.
When not to use
- Inline image insert in a rich-text body — that's an
Editorconcern with its own embed pipeline. - Avatar / single-image cropping where a dedicated cropper does more than file-pick — pair the file picker with a separate cropping component.
Key UX patterns
- Surface accepted file types and max size before upload — and validate client-side before showing a progress bar that's about to fail.
- Drag-and-drop zone shows a clear hover state when the dragged item enters; provide a "or click to browse" affordance for non-drag users.
- Per-file progress, retry on failure, and remove-before-submit are non-negotiable — losing a near-complete upload to a transient error is the canonical FileUpload frustration.
- Auto-upload on drop vs queue-and-submit-later are different flows; pick one and don't mix the metaphors in a single instance.
Accessibility
- The control wraps a real
<input type="file">— visually hidden but reachable via Tab; the visible drag-zone is decorative and clicking it triggers the input. - Drop-only is not enough: keyboard users must be able to open the picker and queue files via Enter / Space; W3C SC 2.5.7 (Dragging Movements) requires this fallback.
- Per-file status (uploading / uploaded / failed) needs
aria-live="polite"announcements; a silent green tick is invisible to non-sighted users. - For multi-file selection, expose count and individual remove buttons with named accessible labels ("Remove filename.pdf").
Drag and drop files here
or click to browse
Editor
When to use
- Long-form content authoring — articles, knowledge-base entries, email composition — where bold / lists / links / images are part of the message.
- Comment fields that benefit from limited formatting (issue trackers, code reviews, internal notes) when plain markdown isn't friendly to non-technical users.
When not to use
- Plain prose with no formatting — use
Textarea; the toolbar adds visual weight and complexity users don't need. - Code or programming input — use a dedicated code editor (Monaco, CodeMirror); the WYSIWYG output mangles indentation and special characters.
- Strongly schema-bound input where every character must conform — rich text encourages free-form expression that won't fit a regex.
Key UX patterns
- Toolbar should expose only the formatting actions the use case actually supports — a 24-button toolbar with no "list" button is a worse experience than a focused 6-button one.
- Provide a keyboard shortcut for every toolbar action and surface them in tooltips (Cmd+B, Cmd+K) — power users live in shortcuts.
- Paste from Word / Google Docs typically arrives with hostile inline styles; sanitise on paste and offer "paste as plain text" as an explicit fallback.
- Persist drafts (autosave or localStorage) — losing rich-text content to a tab close is a worse failure than losing a tweet.
Accessibility
- The content area must be
contenteditablewith a realrole="textbox"andaria-multiline="true"; an unlabelledcontenteditablediv is invisible to assistive tech. - Toolbar buttons are real
<button>s witharia-pressedreflecting current selection state — bold is on / off depending on cursor position. - Provide an accessible name on the editor wrapper ("Comment", "Article body") via
aria-labeloraria-labelledbytied to the visible label above. - Don't trap Tab inside the editor — Tab indents lists if the cursor is in one, but moves to the next form field at top level. Esc-to-blur is a useful escape hatch when the cursor is deep in nested formatting.
TreeSelect
When to use
- Picking a value from a hierarchical taxonomy (folder picker, category tree, organisation chart, geography drill-down: country → state → city).
- Multi-select across a tree where parent toggles propagate to children (or to a tristate of "some children selected").
When not to use
- Flat lists — use
Select/MultiSelect; a tree with no nesting is overhead. - Free-text typeahead through a tree — combine with
AutoCompleteso users can jump to a deeply-nested node by typing instead of expanding three levels. - Always-visible hierarchical navigation — that's a sidebar tree, not a select.
Key UX patterns
- Show the path to the selected node ("Europe / Netherlands / Amsterdam") in the trigger when the leaf alone is ambiguous.
- Expand-collapse persists per session so users returning to the picker don't have to re-navigate to the same branch.
- Multi-select with cascade: parent ✓ selects all descendants; explicit indeterminate state when some descendants are selected.
- Lazy-load deep branches when the full tree is large — emit a load event when a node is first expanded and show a transient loading row.
Accessibility
- Implements the W3C tree pattern:
role="tree"on the wrapper,role="treeitem"witharia-expanded/aria-level/aria-setsize/aria-posinsetper node. - Arrow keys: Right expands or moves to first child, Left collapses or moves to parent, Up / Down navigate visible nodes; Space / Enter selects.
- Announce the selected value as a path, not a leaf — "Amsterdam, Netherlands, Europe" — so screen-reader users get hierarchy context the visual cue conveys at a glance.
Pick a folder
Avatar
When to use
- To identify the actor on a comment, message, activity feed, or assignment row.
- In account menus, presence lists, and member rosters where a face is faster to recognise than a name.
- Stacked in an
AvatarGroupto indicate shared ownership or co-presence.
When not to use
- As a status / count indicator overlaid on another control — that's a
Badge. - To represent a removable selection or filter token — use
Chip, which carries a remove affordance.
Key UX patterns
- Fall back through image → initials → generic icon; never render a broken-image placeholder.
- Initials are at most two characters; derive them from the first and last token of the display name, not the email local-part.
- Pair with a
Tooltipshowing the full name on hover when the avatar appears without an inline label.
Accessibility
- Provide an
altdescribing the person ("Maria Doe"), not the asset ("profile photo"); decorative duplicates of an adjacent visible name should usealt="". - Initials and icon variants need an accessible name via
aria-label— the visual letters alone don't read meaningfully. - Don't rely on colour alone to convey presence or role — pair with an icon or text.
MD
Badge
When to use
- Unread / notification counts overlaid on icons, avatars, or menu items.
- Concise status flags ("New", "Beta", "3") attached to navigation or list rows.
- Truncate large counts to
"99+"; the exact number stops being actionable past that.
When not to use
- As a static categorisation label that stands on its own — use
Tag, which is sized for body context rather than overlay. - To represent a discrete entity the user can dismiss or remove — use
Chip. - For long-form status messages — use
MessageorToast.
Key UX patterns
- A dotless badge (no value) signals "something new" without committing to a count; use it when an exact number isn't available or worth fetching.
- Position consistently — top-right of the host element is conventional and matches OS notification conventions.
Accessibility
- Include the count in the host control's accessible name ("Inbox, 3 unread"), not only on the badge — screen readers won't reliably stitch the two together otherwise.
- Severity colour alone can't convey state (WCAG 2.2 SC 1.4.1 Use of Color); combine with text or an icon.
- Live updates to the count should be wrapped in
aria-live="polite"so the screen-reader announces the change without stealing focus.
3
Tag
When to use
- To label the status of a record in a list or table cell ("Active", "Draft", "Production").
- For severity / category markers next to titles ("Critical", "Low priority").
- To classify an item by attribute when the user can't remove or edit the value from this surface.
When not to use
- For a removable entity (selected user, applied filter, recipient) — that's a
Chip. - For an unread / notification count overlaid on another control — use
Badge. - As a button — tags should not look interactive; if the value is clickable, reach for
ButtonTextorChip.
Key UX patterns
- Limit to a small, controlled set of severities / statuses; arbitrary user-supplied tags trend toward visual noise.
- Pair colour with a leading icon for the canonical states (success / warn / danger / info) so the meaning survives in monochrome.
Accessibility
- Don't rely on colour alone to convey severity (WCAG 2.2 SC 1.4.1) — the textual value carries the meaning.
- Ensure the tag's text contrast against its fill meets WCAG 2.2 SC 1.4.3 (4.5:1 for body text).
Production
Chip
When to use
- To represent a person, file, or other entity in a compact row (recipients, attachments, assignees).
- For applied filters above a results list, where each chip can be removed to relax the query.
- As the rendered tokens of an
InputChipsorAutoCompletewith multiple selection.
When not to use
- For a static, unremovable status or category — that's a
Tag. - For unread / notification counts overlaid on another element — use
Badge. - As a primary call to action — chips are object pills, not buttons.
Key UX patterns
- Surface a remove affordance ("×") whenever the chip is dismissable; clicking it removes only that chip and keeps focus in the parent.
- Combine an avatar / icon on the leading side with the label so the user reads the entity at a glance.
- Keep labels short (one to three words); truncate longer labels with an ellipsis and reveal the full string in a
Tooltip.
Accessibility
- The remove control needs a discrete accessible name ("Remove Maria Doe"); icon-only × buttons read as a bare "button" to a screen reader.
- Announce removals via an
aria-live="polite"region on the parent so non-visual users notice the chip set has shrunk.
Maria Doe
Card
When to use
- To group all the information about one entity (article, product, summary, dashboard tile) into a tappable surface.
- In dashboards and product grids where each item carries a title, supporting content, and at least one action.
- When the surface itself can be the affordance — clicking the card navigates to the detail view.
When not to use
- To group form fields under a legend — that's
FieldSet. - For a collapsible region inside a layout — use
Panel, which carries the toggle affordance. - For undifferentiated boxes — without a clear content unit, a card just adds visual weight to a list.
Key UX patterns
- If the whole card is clickable, give it a single primary action area and avoid nested interactive elements that compete with it (NN/g card-pattern guidance).
- Footer actions read left-to-right by importance; keep at most two actions per card to avoid forcing micro-decisions.
- Maintain a consistent height across a card grid so the row alignment is predictable; truncate long bodies with an ellipsis rather than letting one card distort the row.
Accessibility
- If the entire surface is a link, the title still needs to be the accessible name — wrap the title in the link rather than the whole card, or expose the link via
aria-labelledby. - Avoid putting interactive controls inside an interactive card; nested clicks confuse keyboard users and screen readers.
Quarterly report
Q3 — published 12 Oct 2026
Revenue up 14% over last quarter; the full breakdown by region and channel is attached.
Panel
When to use
- To group a section of content under a labelled header that the user can expand or collapse.
- For settings pages, sidebars, or long forms where the user benefits from progressive disclosure of optional sections.
- When multiple panels can be open at once — for one-at-a-time behaviour use
Accordion.
When not to use
- For a self-contained content unit with its own actions — use
Card. - For grouping form fields under a legend — use
FieldSet; it's the semantic match. - For exclusive (one-at-a-time) collapsible groups — use
Accordion.
Key UX patterns
- Header acts as the toggle; clicking anywhere on the header (not just the chevron) opens or closes the panel.
- Persist expanded state across navigation when it's user-set; reset only when content materially changes.
- Animate the disclosure to make the change of layout legible, but respect
prefers-reduced-motion(WCAG 2.2 SC 2.3.3).
Accessibility
- The header is a
buttonwitharia-expandedreflecting state andaria-controlspointing to the body region (W3C ARIA Authoring Practices — Disclosure pattern). - Focus stays on the header on toggle; never auto-shift focus into newly revealed content unless the user explicitly invoked it.
Account settings
Manage your profile, password, and notification preferences.
FieldSet
When to use
- For groups of form controls that share a common subject (billing address, contact details, shipping options).
- When a set of
RadioButtons shares a question — the legend is the question, the radios are the options. - For checkbox groups where the legend phrases the prompt for the multi-selection.
When not to use
- For non-form content groups — use
CardorPanelto avoid thefieldset/legendsemantic mismatch. - For a single field with its own visible label — the label suffices; a fieldset is overkill.
Key UX patterns
- Phrase the legend as the question or the group's title in plain language ("Shipping address", "How should we contact you?").
- Keep groups under ~7 fields where you can; longer groups read as a long form even with a fieldset around them.
Accessibility
- Backed by the native
<fieldset>+<legend>elements — screen readers announce the legend as part of each contained control's accessible name (WCAG 2.2 SC 1.3.1, SC 4.1.2). - The legend can't be hidden with
display:nonewithout losing the announcement; use a visually-hidden technique if it must be off-screen.
Divider
When to use
- To separate sections of content where whitespace alone is insufficient — long lists, dense forms, sidebar groups.
- Inline (vertical) between adjacent items in a toolbar or breadcrumb to clarify boundaries.
- With an optional inline label ("OR") to mark a clear branching point in a flow.
When not to use
- Between every list item — borders or spacing inside a list component handle that and dividers will compete.
- As decorative chrome on every section — increase whitespace first; reach for a divider only when whitespace alone fails.
Key UX patterns
- Use a single weight per surface; mixing thin and thick rules reads as inconsistency rather than hierarchy.
- Match the divider colour to the surface's neutral border token so it sits one step below body text in contrast.
Accessibility
- Renders as
role="separator"by default; for purely decorative spacing dividers, setaria-hidden="true"so screen-readers don't announce a separator without semantic meaning.
Above
Below
Skeleton
When to use
- While fetching content where the eventual layout is known and stable — list rows, cards, profile blocks.
- For perceived-performance gains on initial load: rendering shapes immediately is faster-feeling than a blank screen plus spinner (NN/g — perceived performance).
When not to use
- For indeterminate operations where you don't know the eventual shape — use
ProgressSpinnerinstead. - For determinate progress (file upload, multi-step task) — use
ProgressBar. - For content that loads in well under ~300ms — the skeleton flash itself becomes noise.
Key UX patterns
- Shape the skeleton to match the eventual content (text-line widths, image aspect ratios) so the swap-in doesn't reflow the page.
- Keep the shimmer / pulse animation subtle; aggressive motion is distracting and may trigger
prefers-reduced-motionoverrides (WCAG 2.2 SC 2.3.3).
Accessibility
- Mark the placeholder with
aria-busy="true"on its container so assistive tech knows the region is loading; flip toaria-busy="false"when content arrives. - Don't read individual skeleton blocks aloud — they have no meaning. Either hide them with
aria-hidden="true"or rely on thearia-busystate on the parent.
Image
When to use
- For meaningful visual content the user may want to inspect at full resolution — product photos, screenshots, illustrations with detail.
- When you need a preview / zoom affordance without writing your own lightbox.
- For lazy-loaded media inside long lists or article bodies, where the native loading attributes alone aren't enough.
When not to use
- For purely decorative graphics — a plain
<img alt="">avoids unnecessary chrome and interaction. - For multi-image galleries with thumbnails — use
Galleria, which handles navigation between items. - For before / after comparison — use
ImageCompare; it's purpose-built for that interaction.
Key UX patterns
- Reserve space via
widthandheight(or aspect ratio) so the layout doesn't shift on load (WCAG 2.2 SC 2.3.1 / Web Vitals CLS). - Show a low-resolution placeholder or
Skeleton-shaped block while the full asset streams in over slow connections.
Accessibility
- Provide a meaningful
altfor content images andalt=""for decorative ones (WCAG 2.2 SC 1.1.1). - Preview / zoom triggers must be keyboard-reachable; the close affordance has a discrete accessible name and Esc dismisses the overlay.
ImageCompare
When to use
- To show the effect of an edit, restoration, retouch, or filter against the original (photo editing, design diffs, before-and-after case studies).
- When the comparison itself is the content — the user benefits from sweeping back and forth rather than seeing two static thumbnails.
When not to use
- For more than two states — use
Galleriaor a simple grid; the slider only meaningfully scrubs between two images. - When the two images differ in dimensions or framing — the slider only works visually when both share the exact same crop and resolution.
Key UX patterns
- Default the divider to ~50% so both states are visible on first paint; let the user drag from there.
- Label each side clearly ("Before" / "After") so the meaning survives even when the divider sits at an extreme.
Accessibility
- The slider thumb must be keyboard-operable as a
sliderrole with Arrow-key support (W3C ARIA — Slider pattern); announce the current position as a percentage so non-visual users know how much of each side is showing. - Each image needs its own
alt; the comparison meta-description ("Before and after retouching") goes on the wrapping figure.
Galleria
When to use
- For sets of related images where users will browse one after another — product photography, portfolios, event galleries.
- When the thumbnail rail (mini-map of the set) is itself a useful affordance — the user can jump to any image, not just step through.
- For lightbox / full-screen preview of a curated set, with arrow-key and swipe navigation.
When not to use
- For mixed-content slides (text, video, embeds) — use
Carousel, which renders arbitrary slot content per slide. - For exactly two images compared side by side — use
ImageCompare. - For a single image with zoom —
Imagealready provides preview without the gallery overhead.
Key UX patterns
- Highlight the current thumbnail in the rail so the user always knows their position in the set.
- Support keyboard navigation (Left / Right arrows to step, Esc to close the lightbox) on top of mouse / swipe.
- Pre-load the next image while the current one is on screen so stepping forward feels instant.
Accessibility
- Each image needs an
altdescribing its content, not "image 3 of 8" — the count is announced separately. - Lightbox / full-screen view follows the W3C Dialog (modal) pattern: trap focus inside, restore focus to the trigger on close, and respect Esc.
- Pause auto-advance on focus / hover and provide a visible play / pause control (WCAG 2.2 SC 2.2.2).
Carousel
When to use
- For browsing a curated set of mixed content (cards, products, testimonials, hero slides) where each slide carries its own copy + actions.
- On surfaces where horizontal screen real estate is at a premium and the user benefits from paging through 2–4 items at a time.
When not to use
- For an image-only set with thumbnails and lightbox — use
Galleria. - For top-of-page hero rotation — usability research (NN/g) consistently shows auto-rotating hero carousels are largely ignored and harm SEO; pick the strongest message and commit.
- For long lists where users want to scan everything — a vertical list or grid beats horizontal pagination for comprehension.
Key UX patterns
- Show pagination indicators (dots or "3 / 8") so the user knows how many slides exist and where they are.
- If auto-rotation is on, pause on hover, focus, and when the page is hidden — and provide a visible pause button (WCAG 2.2 SC 2.2.2).
- Snap to discrete slides on swipe / arrow rather than free-scroll, so the user lands on a complete unit of content.
Accessibility
- Follow the W3C Carousel pattern: previous / next buttons, slide group with
aria-roledescription="slide", and a live region announcing the current slide on change. - Don't trap keyboard focus inside the slide track; Tab should leave the carousel after stepping through controls and slide content.
Chart
When to use
- Quantitative comparisons (bar, line) or part-to-whole breakdowns (pie, doughnut) where reading raw numbers from a table would obscure the trend.
- Dashboards and reports where shape-at-a-glance matters more than per-row precision.
When not to use
- Small data sets where a
DataTablereads faster — three numbers don't need a chart. - Single-metric KPIs — a stat card or
MeterGroupis honest about there being one number.
Key UX patterns
- Charts here are Chart.js under the hood; configure axis labels and units, and never ship a y-axis that just reads 0–100 with no unit.
- Pick a colourblind-safe palette (Okabe-Ito or similar) and never encode the only signal in red-vs-green; pair colour with shape, label, or pattern.
- Truncated y-axes exaggerate differences — start at zero for bar charts unless there's a documented reason not to.
Accessibility
- The canvas is opaque to screen readers; supply an
aria-labelthat summarises the takeaway ("Sales rose 18% Q1 to Q4"), not just "bar chart". - Provide the underlying data as a fallback table for users who can't parse the visual — WCAG 1.1.1 text alternative for non-text content.
- Ensure all encoded series meet 3:1 contrast against the chart background and against each other (WCAG 1.4.11 Non-text Contrast).
DataTable
When to use
- Structured records that share the same set of attributes — orders, users, transactions, log lines — where users compare values across rows.
- When sort, filter, pagination, or selection are required; a plain list won't scale past a screen's worth of rows.
- Reports and admin views where exporting a CSV is a likely follow-up.
When not to use
- Cards or media-led item grids — use
DataViewto render rich item layouts (product catalogue, blog list). - Hierarchical data — use
TreeTablewhen rows can expand into children. - A handful of items where structure adds noise — a simple
<ul>with semantic markup is enough.
Key UX patterns
- One sort indicator visible at any time on the active column; clicking the same header cycles asc → desc → unsorted.
- Sticky header on long tables; right-align numeric columns, left-align text — NN/g data-table guidance.
- Empty, loading, and error states for the row region — never just an empty body with no explanation.
Accessibility
- Render real
<table>,<thead>,<th scope>markup so screen readers can announce row + column headers (WCAG 1.3.1 Info and Relationships). - Use
aria-sorton sortable column headers and announce the sort state change. - Horizontal scroll for narrow viewports must keep the table reflowable per WCAG 1.4.10 Reflow; never trap users in a horizontal pan.
| Name | Category | Stock |
|---|---|---|
| Apple | Fruit | 12 |
| Bread | Bakery | 5 |
| Milk | Dairy | 8 |
DataView
When to use
- Item collections where each row deserves its own visual layout — product cards, blog posts, profiles, gallery thumbnails.
- When users browse and scan rather than compare cell-to-cell; the list/grid toggle lets them pick the density.
When not to use
- Structured tabular data — use
DataTable; cards sacrifice column alignment that makes scanning numbers possible. - Hierarchical data — reach for
TreeorTreeTable.
Key UX patterns
- Provide both list and grid layouts and remember the user's choice across sessions.
- Pair with
Paginatorfor long sets, or virtualise viaVirtualScrollerfor thousands of items. - Card content has a clear hierarchy — primary identifier, supporting metadata, then actions; don't bury the title under chips.
Accessibility
- Render the collection as a list (
<ul>/<li>) so assistive tech announces item count and position. - Each card needs a single primary link or button as its accessible name; avoid nesting interactive elements inside an outer card link.
- Layout switches must keep focus on the same item — don't reset to the top of the list.
OrderList
When to use
- The user owns a single ordered set and rank matters — playlist queue, todo priority, dashboard widget order.
- When a stable, persisted order is the output of the interaction.
When not to use
- Moving items between two lists — use
PickList, which is purpose-built for source/target transfers. - Selecting a subset without re-ordering — a
MultiSelectorListboxis lighter-weight.
Key UX patterns
- Always provide on-screen up/down/top/bottom buttons in addition to drag — drag-only excludes keyboard, touch with limited dexterity, and assistive tech.
- Show the new position during drag (placeholder slot, drop indicator); commit on drop, not on hover.
- Selection and ordering are separate concerns — clicking selects, the rank controls move; don't conflate them.
Accessibility
- The list is a
listboxwith selectable options; movement actions need explicit accessible names ("Move up", "Move to top"). - After a move, announce the new position via a polite live region — "Item 3 moved to position 1".
- Keyboard equivalent for drag is required (W3C ARIA APG): typically Space to grab, Arrow keys to move, Space to drop, Esc to cancel.
Title
Hero image
Body copy
Call to action
OrganizationChart
When to use
- Showing reporting structure, team composition, or any strict parent/child hierarchy where the diagrammatic shape carries meaning.
- Family trees, decision trees, or any diagram where horizontal sibling relationships matter.
When not to use
- Browsing or selecting from a hierarchy —
Treeis denser and keyboard-friendly for navigating folders or categories. - Hierarchy with per-node tabular attributes —
TreeTableshows columns alongside the structure. - Networks with cycles or multiple parents — an org chart implies a strict tree; use a graph visualisation library instead.
Key UX patterns
- Collapse deep branches by default and let users drill down; a 200-node org rendered flat is unreadable.
- Make each node a meaningful card (name, role, photo) rather than just a label — this is a visualisation, not a tree control.
- Provide pan + zoom for large charts; preserve the focused node when the viewport shifts.
Accessibility
- Provide a parallel text representation (nested list) for users who can't parse the visual hierarchy — WCAG 1.1.1.
- Each node card is a button or link with its own accessible name; the SVG/diagram chrome is decorative
aria-hidden. - Do not rely on connecting lines alone to convey relationships — programmatic structure should reflect the parent/child mapping.
CEO
CTO
Lead Eng
COO
Tree
When to use
- Navigating or selecting from nested collections — file system, category taxonomy, sitemap, nested settings.
- When users need to see ancestor context while drilling down; the expand/collapse pattern keeps siblings visible.
- Multi-select within a hierarchy with checkbox selection mode — useful for permission editors and tag pickers.
When not to use
- Each node has rich tabular attributes — use
TreeTableso columns sit alongside the structure. - Visualising a single org diagram top-down —
OrganizationChartis the right shape. - Flat collections — a
ListboxorDataTableis simpler and faster to scan.
Key UX patterns
- Expand-on-click of the disclosure caret only; clicking the label selects rather than toggling expansion.
- Lazy-load deep branches and show a loading state on the parent node — never block the UI on an unexpanded subtree.
- Persist expansion state when filters or selection change so users don't lose their place.
Accessibility
- Implement the W3C ARIA tree pattern:
role="tree", childrole="treeitem",aria-expandedon parent items,aria-selectedon selected. - Keyboard: Arrow Up/Down moves between visible items, Right expands or moves to first child, Left collapses or moves to parent, Enter activates.
- For checkbox selection mode, expose the tri-state (checked / partially-checked / unchecked) via
aria-checked="mixed".
Documents
Pictures
TreeTable
When to use
- Hierarchical records that also carry tabular attributes — folder structure with size + modified date, project plan with hours + status, account chart with balances.
- When users want to compare metrics across siblings without losing the parent/child relationship.
When not to use
- Plain hierarchies without per-node attributes —
Treeis lighter and reads faster. - Flat tabular data —
DataTable; the disclosure column is dead weight if nothing nests.
Key UX patterns
- The disclosure caret sits in the first column only; subsequent columns stay aligned regardless of nest depth, with indent on the label.
- Aggregate metrics on parent rows where it makes sense (sum, count) — make clear whether a parent's value is its own or rolled up from descendants.
- Sort scopes to siblings within a parent, not across the whole flattened set; otherwise you destroy the hierarchy.
Accessibility
- Use the W3C ARIA
treegridpattern:role="treegrid"with row-levelaria-level,aria-expanded,aria-posinset,aria-setsize. - Keyboard: Arrow keys traverse cells; Right at column 0 expands a row, Left collapses or moves to parent, Enter activates the active cell's control.
- Render real
<th scope>headers so column context is announced even when rows are deeply nested.
| Name | Size |
|---|---|
| No records found | |
RelationMatrix
When to use
- The intersection is the data — RACI charts, role-vs-permission grids, feature × plan matrices, compatibility tables, capability rubrics.
- You want a glanceable signal (tick / dash / cross) plus a verbal label that survives screen readers and HC themes.
When not to use
- Plain tabular data — use
DataTable; the relation-glyph cell wastes space when the cell value is the real datum. - Continuous data (heatmaps, score gradients) — that's a
Chartwith a sequential colour scale, not a discrete tone enum. - Hierarchical relationships — use
TreeorTreeTable.
Key UX patterns
- Glyph + tone + verbal label: never rely on colour alone — every legend entry carries a glyph so the matrix reads in HC themes and for colour-blind users.
- Sticky row and column headers when the matrix scrolls; the relationship loses meaning the moment a header drifts off-screen.
- Optional row/column totals counting cells of a target tone — useful for compliance matrices ("how many controls covered per framework").
Accessibility
- Real
<table>with<th scope="col">on column headers and<th scope="row">on row headers — assistive tech announces row + column context for every cell automatically. - Each cell has an
aria-labelcomposed as "{row}, {legend label}, {column}" so the relationship is spoken in plain language, not as a tone or glyph. - When interactive, cells render as real
<button>s inside the cell — full keyboard support (Tab, Enter, Space) without extra JS.
| Read | Edit | Delete | Transfer | |
|---|---|---|---|---|
| Owner | ||||
| Admin | ||||
| Member | ||||
| Guest |
- Allowed
- Conditional
- Denied
PickList
When to use
- Building a subset from a larger pool — assigning permissions to a role, adding members to a group, choosing fields for an export.
- When users need to see both what's available and what's selected at the same time, plus the order of selected items.
When not to use
- Re-ordering a single list — that's
OrderList. - Picking from a short list of options — a
MultiSelectwith chips is lighter and far less screen real estate. - Mobile-first contexts — the two-column layout collapses badly under 600px.
Key UX patterns
- Provide both single-item and bulk-transfer controls (move all, move selected) plus reorder controls within the target.
- Search/filter on the source list when the pool is more than a screen tall.
- Persist the visual position of items so the user can find what they just moved — focus follows the moved item to its new home.
Accessibility
- Each list is a
listboxwith its own visible label ("Available", "Selected"); transfer buttons name the direction explicitly ("Move to Selected"). - Announce transfers via a polite live region — "3 items moved to Selected"; otherwise screen reader users can't tell anything happened.
- Keyboard: Tab between listboxes and the transfer column; Arrow keys + Space for selection within a listbox; Enter activates the directional move.
Newsletter
Product updates
Surveys
Order receipts
Timeline
When to use
- Sequential events anchored in time — order status (placed → packed → shipped → delivered), commit history, comment threads, audit trail.
- Cases where the gap between events matters and the visual axis communicates duration or stage.
When not to use
- Generic ordered lists with no temporal meaning — a plain list or
DataTablesorted by a date column is clearer. - Heavy comparative analysis across events — a
Chartserves quantitative trends better.
Key UX patterns
- Each event has a clear marker (icon, status colour) tied to event type so users can scan the column at a glance.
- Show absolute timestamp on hover/focus and relative time inline ("2 hours ago") — both readings serve different mental models.
- Group dense activity by day or week to keep long timelines navigable; collapse old chunks behind a "show earlier" affordance.
Accessibility
- Render as an ordered list (
<ol>) so screen readers announce sequence; the visual rail is decorativearia-hidden. - Don't encode event type by colour alone — pair the marker colour with an icon and visible label (WCAG 1.4.1 Use of Colour).
- Use machine-readable
<time datetime>elements for timestamps so assistive tech can read them in the user's preferred format.
2026-04-01
Ordered — 2026-04-01
2026-04-02
Shipped — 2026-04-02
2026-04-04
Delivered — 2026-04-04
VirtualScroller
When to use
- Lists or grids with thousands of items where rendering them all would block the main thread or balloon memory.
- Wrapping a
DataView,DataTable, or custom item template when paging is undesirable (chat history, log viewers, infinite feeds).
When not to use
- Short lists — the recycling overhead is wasted and you lose native browser find-on-page for items below the fold.
- Items with wildly variable heights you can't measure ahead of time — virtualisation needs predictable item-size to position the scrollbar correctly.
- This is structural; don't ship it as a UI primitive on its own — wrap a list, not a button.
Key UX patterns
- Provide explicit
item-sizematching the rendered row height; mismatches cause flicker and scroll-jank. - Show a loading sentinel while data fetches in if the list is async-paged underneath.
- Preserve scroll position across navigation so users return to where they left off in long feeds.
Accessibility
- Browser find-in-page (Cmd/Ctrl-F) won't see off-DOM items — provide an explicit search/filter input over the data.
- Programmatic
aria-rowcount/aria-setsizemust reflect the full virtual size, not the rendered window. - Keyboard scroll keys (PageUp/PageDown, Home/End) must move through the virtual list, not just the rendered slice.
Terminal
When to use
- Developer-facing tooling that genuinely benefits from a CLI metaphor — a debug console, an admin REPL, a tutorial sandbox.
- Documentation demos that show command-line workflow without making the user open a real shell.
When not to use
- End-user form input. Free-text commands are not a substitute for
InputText,Select, or any structured control — discoverability and validation collapse without them. - Anything where typos cost the user — terminals lack inline validation, autocomplete, and undo by default.
Key UX patterns
- Persist a prompt prefix and welcome message so the surface reads as a console, not a stray text box.
- Provide history (Arrow Up/Down) and at minimum a
helpcommand — the metaphor implies them. - Echo input on Enter and clear the field; never let it look like the command was lost.
Accessibility
- The output region is a
loglive region (role="log",aria-live="polite") so new lines are announced. - Maintain a single focusable input with a visible label ("Terminal command"); the rendered output is read-only text.
- Honour reduced-motion preference for any caret blink or scroll animation (WCAG 2.3.3).
Welcome to the matrix terminal
$
Message
When to use
- Persistent status banners attached to a page or section (server outage, draft auto-saved, payment failed) that the user should be able to re-read.
- Confirmation of a completed background action that needs more than a flash — "Your export is ready, click to download".
When not to use
- Transient acknowledgements that should auto-dismiss — use
Toast. - Field-level validation tied to a single control — use
InlineMessagenext to the input.
Key UX patterns
- Severity (info / success / warn / error) drives icon and colour; never rely on colour alone — keep the icon and the text.
- Closable banners need an explicit dismiss button; non-closable banners must be quiet enough to live on the page indefinitely.
Accessibility
- Use
role="status"for info / success androle="alert"for warn / error so assistive tech announces them when injected. - Don't trap focus inside the banner; if there's an action inside, expose it as a normal focusable button in document order.
InlineMessage
When to use
- Field-level validation feedback that needs to sit right next to the offending input.
- Short helper / hint text inside a form group where a full
Messagebanner would dominate.
When not to use
- Page- or region-level status — use
Messageso the alert doesn't get visually swallowed. - Transient acknowledgements after a global action — use
Toast.
Key UX patterns
- Render adjacent to the field it describes and pair it with the field's
isInvalidstate — don't rely on colour alone. - Keep the message a single, specific sentence — "Email is required" beats "This field is invalid".
Accessibility
- Wire the message to its input via
aria-describedbyso screen readers announce it together with the label. - Use
role="alert"only when the message appears in response to a submit-time error; for static helper text leave the role off.
ProgressBar
When to use
- Operations with a knowable end — file uploads, installs, multi-stage imports, video buffering.
- Multi-step flows where rendering "step 3 of 5" as a bar is more glanceable than counting
Steps.
When not to use
- Indeterminate "we're working" indicators — use
ProgressSpinnerso the user doesn't expect a percentage. - Static gauges of a fixed-value-within-a-range (disk used / quota) — use
MeterGroupwithrole="meter".
Key UX patterns
- Show numeric progress alongside the bar when the user might want to predict completion ("62% — about 30s remaining").
- Don't snap backwards; if the estimate has to change, ease forward or hold position rather than jumping back.
Accessibility
- Use
role="progressbar"witharia-valuenow/aria-valuemin/aria-valuemax; provide anaria-labeldescribing what is progressing. - Updates should be polite — frequent
aria-valuenowchanges can swamp a screen reader; debounce announcements to roughly every 10%.
ProgressSpinner
When to use
- Short waits where the duration is unknown — network requests, background queries, lazy-loaded views.
- In-button busy states once the user has triggered a submit and the response hasn't returned yet.
When not to use
- Operations where percentage is calculable — use
ProgressBarso the user can plan around the wait. - Long waits (more than ~10 seconds) — pair with explanatory text or upgrade to a determinate bar; an endlessly spinning ring reads as "broken".
- Skeleton-eligible content (lists, cards, tables) — use
Skeletonplaceholders for a less anxious wait.
Key UX patterns
- Render after a small delay (~150ms) to avoid flash on fast responses; if the work completes inside that window, no spinner needed.
- Disable the trigger that started the work so the user doesn't double-submit while the spinner is showing.
Accessibility
- Use
role="status"with anaria-labellike "Loading" so the activity is announced once, politely. - Honour
prefers-reduced-motion— the rotating ring is purely decorative; under reduced motion swap to a pulse or hold the indicator static.
MeterGroup
When to use
- Quota / capacity displays — disk used vs free, plan limits, points balance, budget remaining.
- Composite breakdowns of a fixed total (storage by file type, traffic by source) where each segment shares one bar.
When not to use
- A task in progress — use
ProgressBar; meter is for steady-state values, not advancing percentages. - A single binary or threshold signal — a coloured
BadgeorTagreads more cleanly than a one-segment meter.
Key UX patterns
- Pair each segment with a legend that names it and shows its absolute value, not just its colour swatch.
- Reserve red for "you're over budget / nearly full"; semantic threshold styling beats pure colour-by-magnitude.
Accessibility
- Each segment carries
role="meter"witharia-valuenow/aria-valuemin/aria-valuemax— distinct fromprogressbar, which implies forward motion. - Provide an
aria-labelper meter ("Disk used by media") and announce threshold-crossings as separaterole="status"messages rather than via the meter itself.
Used30%Reserved20%Free50%
BlockUI
When to use
- Wrap a region (form, panel, table) while it's saving so the user can't double-submit or edit stale data.
- Lock parts of the UI behind an authorisation or licensing gate when the rest of the screen should remain interactive.
When not to use
- Full-app blocking modals — use
Dialogwith a backdrop; BlockUI doesn't take focus or trap dismissal. - Brief in-flight indicators on a single button — disable the button and pair it with a
ProgressSpinnerinstead.
Key UX patterns
- Pair the overlay with explicit context — a spinner plus "Saving…" beats a silent grey wash that looks like a render bug.
- Block the smallest region you can; whole-page blockers feel heavy and slow even when the wait is short.
Accessibility
- Set
aria-busy="true"on the wrapped region so assistive tech announces it as in-progress; pair with arole="status"live region describing what's happening. - Keep WCAG 2.4.7 in mind — focus moves elsewhere while the region is blocked must remain visible; if focus was inside the blocked region, move it to a sensible neighbour first.
- Make underlying content
inert(or settabindex="-1"on its focusables) so Tab doesn't dive into a region the user can't operate.
Submitting order
Hold tight while we process your payment — this normally takes a few seconds.
InPlace
When to use
- Editable single fields embedded in otherwise read-only content — page titles, list-item labels, profile fields.
- Dense tables or canvases where a permanent input frame would look noisy and most cells stay untouched.
When not to use
- Form-shaped data entry (registration, checkout) — exposing every field upfront is faster and clearer than chasing affordances.
- Critical or destructive edits — wrap in a
Dialogwith explicit Save / Cancel; InPlace's commit-on-blur is too easy to trigger accidentally.
Key UX patterns
- Show, then edit — display mode reads as text but signals editability on hover (subtle underline, pencil icon, cursor change).
- Define a clear commit rule — Enter or blur saves, Esc cancels — and surface a tiny inline indicator while saving so the user knows the value is in flight.
Accessibility
- The display element is a focusable button (
aria-label="Edit document title") that swaps to the input on activation; users navigating with Tab must be able to reach it. - Move focus into the input on enter and back to the display on commit / cancel; never strand focus on a now-hidden node.
- Announce the transition — a brief
aria-live="polite"message confirms the new value or surfaces a save error.
Sales 2026 — Q3
Accordion
When to use
- Long-form content split into related sections (FAQ, settings groups, documentation chapters) where users only need a few at a time.
- Mobile or narrow layouts where horizontal
Tabswould wrap or scroll awkwardly.
When not to use
- Mutually exclusive sections of comparable importance shown side by side — use
Tabs; horizontal selection conveys "alternative views". - Linear progress through a wizard — use
Steps; an accordion lets users open sections out of order.
Key UX patterns
- Decide up front whether multiple panels can be open simultaneously; a single-open accordion behaves more like Tabs and may confuse users who expect to compare sections.
- Animate expand / collapse fast (~150ms) and keep the trigger anchored — the section headers should not jump around as panels open.
- Persist open / closed state across navigation when the user is comparing sections; resetting on every render is a regression.
Accessibility
- Follow the W3C ARIA accordion pattern: each header is a button (
aria-expanded,aria-controlspointing to its panel), each panel usesrole="region"witharia-labelledby. - Tab moves between headers; Up/Down (and optional Home/End) cycle through them per the pattern; Enter or Space toggles the focused panel.
- Don't hide collapsed content from search / find-in-page tools without a visible affordance — users can't find what they can't see.
Tabs
When to use
- Mutually exclusive views of the same subject (Profile / Account / Notifications) where the user benefits from never scrolling past sections they don't need.
- Inside dialogs or panels with limited vertical space — tabs keep the chrome compact.
When not to use
- Long-form sequential content the user should read end-to-end — use a single page or
Accordion. - Step-by-step flows with order — use
Steps; tabs imply free navigation between equal-weight views. - Navigation between sections of an app — use
TabMenuor top-level navigation; Tabs are for content within a page, not between pages.
Key UX patterns
- Keep tab labels short and parallel ("Profile", "Account" — not "Your profile", "Manage account") so the strip scans cleanly.
- Reflect the active tab in the URL or state so deep-links land in the right panel; users routinely share tab-deep links.
- If labels overflow on narrow widths, scroll the strip rather than wrapping or hiding tabs in a "more" menu — wrapped tabs lose their selector affordance.
Accessibility
- Follow the W3C ARIA tabs pattern:
role="tablist"wraps a series ofrole="tab"headers, each witharia-selected+aria-controls; panels userole="tabpanel"witharia-labelledby. - Single tab stop into the tablist; Arrow keys move between tabs; Home / End jump to first / last; Enter / Space activate when activation isn't automatic.
- Manual vs automatic activation: pick one and stay consistent — automatic switches panels on focus, manual waits for Enter / Space.
Splitter
When to use
- Editor / IDE layouts where the user wants to give one pane more room — file tree + editor, message list + reading pane, table + detail.
- Side-by-side comparison views where the right ratio depends on the user's content.
When not to use
- Themed scrollable regions inside a fixed layout — use
ScrollPanel; that's a styling concern, not a resize one. - Hierarchical or modal views — a
DrawerorDialogconveys "this opens over the page" more clearly than a moveable seam.
Key UX patterns
- Persist the split ratio per layout / user — re-resetting on reload undoes the customisation that made the splitter worth it.
- Set sensible
min-sizeper pane so the user can't accidentally collapse one to invisibility; offer an explicit collapse / expand affordance for that. - Show a clear hover affordance on the separator (cursor change, subtle highlight) and a stronger active style during drag.
Accessibility
- The separator is a focusable element with
role="separator",aria-orientation="vertical", andaria-valuenow/aria-valuemin/aria-valuemaxreflecting the current ratio. - Keyboard pattern: Arrow keys move the separator in small steps; Home / End jump to the bounds; Enter or Space could collapse / restore the adjacent pane.
- Provide a non-drag fallback ("Reset layout", "Maximise editor") — pointer-only resize excludes anyone who can't operate a fine-grained drag.
File tree
Editor
ScrollPanel
When to use
- Bounded regions inside a page that must scroll independently of the page itself (chat windows, sidebars, code blocks).
- Dense layouts where native browser scrollbars look heavy or break the visual language of the rest of the surface.
When not to use
- Resizable two-pane layouts — use
Splitter; ScrollPanel doesn't give the user control over the split. - Page-level scrolling — let the browser handle it; replacing it with a styled scroller breaks history-restoration and find-in-page.
- Long lists / tables where rendering all rows is expensive — use
VirtualScrollerfor windowed rendering.
Key UX patterns
- Keep scrollbar appearance and behaviour close to the OS conventions; deviating ("invisible until hover", non-standard hit areas) costs more clarity than it earns in style.
- Maintain native scroll semantics — wheel, trackpad, swipe, keyboard PageUp / PageDown — even if you're skinning the bar.
- Render gradient fades at the top / bottom when content overflows so users know there's more out of view.
Accessibility
- The scroll container should be keyboard-focusable (
tabindex="0") so Arrow keys, PageUp / PageDown, and Home / End scroll it without requiring pointer use. - Don't override the scrollbar to the point that it's invisible until hover — sighted users with motor difficulties rely on a visible thumb to grab.
Line 1
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10
Line 11
Line 12
Line 13
Line 14
Line 15
Line 16
Line 17
Line 18
Line 19
Line 20
Toolbar
When to use
- Editor / canvas surfaces with a stable cluster of actions (Save, Undo, Format, Insert) that would otherwise litter the page chrome.
- Above tables and lists for bulk actions on selection, paired with a clear empty-selection state.
When not to use
- A single primary call-to-action — a bare
ButtonPrimaryis enough; a toolbar with one button reads as "where are the other actions?". - Navigation between sections — that's
Menubar/TabMenu; a toolbar is for actions, not destinations.
Key UX patterns
- Group related buttons (formatting / structure / insert) and use
Dividerbetween groups so the strip parses faster than as a flat row. - Use
start/endslots to push primary actions to the leading edge and overflow / utility actions to the trailing edge. - For dense toolbars, fall back to icons-only with a
Tooltipper button; keep at least the primary action labelled in text.
Accessibility
- Follow the W3C ARIA toolbar pattern: container has
role="toolbar"with anaria-label; the toolbar takes a single tab stop and Arrow keys move within it. - Icon-only buttons must carry an accessible name (
aria-label); the visibleTooltipalone isn't a programmatic name. - Disabled buttons stay focusable so users can discover why they're disabled — pair the disabled state with a tooltip explaining the precondition.
CorporateHeader
When to use
- Marketing or institutional sites with deep navigation (products, solutions, company) and a standing call-to-action like "Contact sales".
- When a thin utility strip (contact, support, login, language) should sit above the main navigation.
When not to use
- Conversion-focused landing pages — use
PromotionHeader; deep nav competes with the single goal. - Authenticated app shells — use
AppHeader; marketing nav isn't task navigation.
Accessibility
- Renders a
<header>landmark with a labelled primary<nav>; parent nav items advertisearia-haspopup. - The utility bar uses a strong inverse surface so its links clear contrast in every theme.
CorporateFooter
When to use
- Content-rich sites that need a directory of links (products, solutions, company, resources) plus newsletter capture and legal links.
- When the footer doubles as a site map for SEO and orientation.
When not to use
- Single-goal landing pages — use
PromotionFooter; a link directory dilutes the conversion focus. - App shells — use
AppFooter; a fat footer is dead weight in a productivity surface.
Accessibility
- Renders a
<footer>landmark; each link column is a labelled<nav>and the newsletter input has an associated<label>. - Sits on the page surface and is separated by a rule, so every nested surface clears non-text contrast.
PromotionHeader
When to use
- Campaign and product-launch landing pages with one goal and in-page anchor links (Features, Pricing, FAQ).
- When a time-boxed promo (discount, countdown) should ride a dismissible announcement bar.
When not to use
- Sites that need deep multi-level navigation — use
CorporateHeader. - Authenticated apps — use
AppHeader.
Accessibility
- The announcement dismiss control is a real
<button>with an accessible name; the anchor nav is a labelled<nav>. - The announcement bar uses a strong inverse surface for guaranteed contrast across themes.
Launch week — 30% off ends Friday. Claim offer
PromotionFooter
When to use
- Landing pages that want one last conversion push ("Ready to start?") above a distraction-light row of links.
- When the footer should stay minimal — a couple of legal/social links and a copyright line.
When not to use
- Content-rich sites needing a link directory — use
CorporateFooter. - App shells — use
AppFooter.
Accessibility
- Renders a
<footer>landmark with a labelled footer<nav>. - The conversion band is a strong inverse surface carrying a light CTA that inverts safely across all themes.
AppHeader
When to use
- Authenticated app shells that need persistent global search, notifications, and an account menu, paired with a left sidebar for primary navigation.
- When a breadcrumb context row helps users locate themselves inside a deep app hierarchy.
When not to use
- Marketing sites — use
CorporateHeaderorPromotionHeader. - When primary navigation belongs in the top bar itself rather than a sidebar.
Accessibility
- The search input is labelled, the breadcrumb marks the current page with
aria-current, and the notification button exposes its unread count in its accessible name. - Emits
toggle-sidebarso the host wires the hamburger to its navigation drawer.
AppFooter
When to use
- App shells that benefit from a persistent status line — system health, build version, and a couple of support links.
- When you want a quiet footer that doesn't compete with the work area.
When not to use
- Marketing or content sites — use
CorporateFooterorPromotionFooter. - When no persistent chrome is needed at the bottom — omit a footer entirely.
Accessibility
- Renders a
<footer>landmark with a labelled footer<nav>; the status dot is paired with a text label, not colour alone. - Status colours and the version stamp clear text and non-text contrast on the page surface across themes.
Breadcrumb
When to use
- Hierarchical sites where users can be three or more levels deep (catalogues, file systems, settings trees) and need a one-click jump to any ancestor.
- Search-result landing pages — orient the user inside a structure they didn't navigate down through.
When not to use
- Step-by-step flows (checkout, onboarding) — use
Steps; breadcrumbs imply free navigation back, steps imply forward progress. - Section navigation within a single page — use
TabMenuorTabs; breadcrumbs convey hierarchy, not lateral choice. - Flat sites with two levels — the breadcrumb adds chrome for no benefit.
Key UX patterns
- The current page is the rightmost item and is rendered as plain text, not a link — clicking your own page is a no-op that confuses users.
- Truncate intermediate items with an ellipsis menu when the trail overflows; never wrap onto a second line.
- Mirror the page hierarchy exactly — a breadcrumb that doesn't match the URL tree erodes trust faster than any other navigation bug.
Accessibility
- Wrap in
<nav aria-label="Breadcrumb">per the W3C breadcrumb pattern; the list is an ordered list, items are links except the last. - Mark the current page with
aria-current="page"on the final item so screen readers announce it as the current location. - Separators (chevrons, slashes) are decorative — hide them from assistive tech via
aria-hidden; don't rely on them being announced.
Steps
When to use
- Multi-page flows with a known sequence — checkout, onboarding wizards, multi-stage configuration.
- Anywhere users benefit from seeing how much remains before they invest more keystrokes ("step 2 of 4: shipping").
When not to use
- Free navigation between sections of a page — use
Tabs; Steps imply order and forward motion. - Hierarchical location — use
Breadcrumb; Steps don't represent the site tree. - Continuous progress (uploads, installs) — use
ProgressBarinstead of discrete step pips.
Key UX patterns
- Decide whether past steps are click-to-revisit or locked — both are valid, but mixing them silently confuses users; if revisitable, dirty / pristine state must round-trip cleanly.
- Keep step labels short and verb-led ("Shipping", "Pay", "Review") so the strip stays scannable on narrow viewports.
- Provide explicit Back / Next buttons inside the step content — relying on users to click step pips alone makes the flow feel hidden.
Accessibility
- The current step gets
aria-current="step"; visited and unvisited steps are distinguished via accessible text, not colour or icon alone. - If steps aren't clickable, render them as plain text so screen readers don't promise a navigation that isn't there.
- Announce step transitions via a polite live region ("Now on step 2 of 4: shipping") so screen-reader users get the same orientation as sighted users.
Paginator
When to use
- Tables and lists with hundreds of rows where rendering all of them would hurt performance or scanning.
- Server-driven results where each page is a fresh fetch and "load all" isn't realistic.
When not to use
- Streams the user expects to scroll — feeds, chat, logs — use
VirtualScrolleror infinite scroll instead. - Small lists (under ~50 items) — render them all; pagination chrome only adds friction.
Key UX patterns
- Show "First / Prev / 1 2 3 / Next / Last" plus the active range ("Showing 21–40 of 247") so users always know where they are.
- Pair with a rows-per-page selector when result counts vary widely; persist the choice so it survives a navigation.
- Keep the URL or state in sync with the current page so back / forward and refresh return the user to the same slice.
Accessibility
- Mark the active page with
aria-current="page"; previous / next links carry accessible names ("Previous page", "Next page") rather than only chevrons. - Keyboard pattern: Tab into the strip, Arrow keys to traverse pages, Home / End jump to first / last; Enter activates the focused page.
- After a page change, announce the new range via a polite live region so screen-reader users get the same orientation as sighted users.
TabMenu
When to use
- Top-of-page section navigation where each tab maps to a distinct route or view (Profile / Billing / Notifications).
- When the selection should persist via the URL so the active section survives reload and is shareable.
When not to use
- In-page tabs that swap content within the same view — use
Tabs, which carries its own panels and keyboard model. - More than ~7 destinations or destinations that aren't peers — promote to a
Breadcrumb+ page-level navigation pattern.
Key UX patterns
- One tab is always selected; the active tab carries a clear underline or fill so the current location reads at a glance.
- Tabs wrap or scroll on narrow viewports — never silently truncate; off-screen items must stay reachable.
Accessibility
- Render as a
navlandmark with a list of links — TabMenu is navigation, not the W3C tab pattern, so don't applyrole="tab". - Give the nav an accessible name (
aria-label) so screen-reader users can distinguish it from other navs on the page.
Menu
When to use
- Action lists hung off a button — row-level "more" overflows, account menus, sort/filter pickers.
- Grouping 4+ related actions that would otherwise crowd a toolbar.
When not to use
- Top-level horizontal navigation — use
MenuBar, orMegaMenuif items expand into multi-column panels. - Items with their own submenus — promote to
TieredMenu; flatMenucan't host cascading children. - Always-visible sidebar navigation that needs collapsible groups — use
PanelMenu.
Key UX patterns
- Open / close on its trigger; close on Esc, outside click, and after activating an item.
- On open, move focus to the first menu item (or the last-used one); on close, restore focus to the trigger.
Accessibility
- Follow the W3C menu pattern:
role="menu"on the list,role="menuitem"on each row; Up / Down arrows traverse, Home / End jump to ends, Enter or Space activates. - The trigger declares
aria-haspopup="menu"and togglesaria-expanded; without these, AT users can't tell the button opens anything.
MenuBar
When to use
- Productivity / desktop-class web apps where a familiar File / Edit / View / Help bar is expected.
- Top-level navigation with shallow submenus exposing related commands (cascading two or three levels deep).
When not to use
- E-commerce or marketing nav with rich category panels — use
MegaMenufor multi-column reveal. - Section navigation that just switches views —
TabMenureads more like content tabs. - Mobile-first surfaces — collapse to a drawer; a menubar with submenus is cramped on touch.
Key UX patterns
- Top-level items are always visible; submenus open on hover and on click / Enter so keyboard and pointer users have parity.
- Once a submenu is open, hovering a sibling top-level item swaps in its submenu without an extra click — the desktop-app convention.
Accessibility
- Follow the W3C menubar pattern: Left / Right move between top-level items, Down opens the submenu and lands on its first item, Esc closes one level at a time.
- Use
role="menubar"on the bar,role="menu"on each submenu, andaria-haspopup+aria-expandedon items that own a submenu.
MegaMenu
When to use
- E-commerce, publisher, or large-product-catalog sites where a single category fans out into dozens of links plus images / promos.
- Information-dense top navigation that benefits from grouped headings, columns, and visuals — not just a flat list.
When not to use
- Small navs with a handful of links — a plain
MenuBarorTabMenuis calmer and faster. - App-style menubars with command lists — use
MenuBarfor File / Edit / View patterns. - Mobile-first surfaces — the wide panel doesn't fit; collapse into a stacked drawer.
Key UX patterns
- Open the panel on hover after a short intent delay (~150ms) so a passing pointer doesn't trigger it; click / Enter must still open it for keyboard and touch users.
- Group links under clear section headings; the panel is a navigation index, not a marketing page.
- Close on Esc, on outside click, and when focus leaves the menu — never trap users inside the panel.
Accessibility
- Follow the W3C menubar pattern with one key change: each panel is a navigation region of links, so use
role="menu"on the panel only when items behave like menuitems; for plain link lists, render realnav+ulmarkup and skip menu roles. - Tab moves out of the menu rather than between cells — keyboard users escape easily; arrow keys traverse within the open panel.
TieredMenu
When to use
- Action lists where some items have related sub-actions (Export ▸ CSV / JSON / PDF; Move ▸ Folder A / Folder B).
- Right-click / overflow menus that need shallow nesting without growing into a full panel.
When not to use
- Flat action lists with no nesting —
Menuis lighter and avoids the chevron noise. - Persistent sidebar navigation —
PanelMenustays expanded inline rather than fanning out as overlays. - Top-level horizontal navigation —
MenuBarorMegaMenufit that shape.
Key UX patterns
- Submenus open after a short hover-intent delay and on Right-arrow / Enter for keyboard users; only one submenu chain is open at a time.
- Limit nesting to two or three levels — deep cascades become unreachable on touch and tedious on keyboard.
Accessibility
- Follow the W3C menu pattern: Up / Down within a level, Right / Enter opens a submenu, Left / Esc closes back to the parent.
- Items that own a submenu need
aria-haspopup="menu"andaria-expanded; the submenu trigger keeps focus until it's actually opened.
PanelMenu
When to use
- App sidebars where users navigate sections grouped under collapsible headers (Admin ▸ Users / Roles / Permissions).
- Documentation or settings pages with many destinations — keeping the active section expanded shows context without overwhelming the rest.
When not to use
- Overlay action menus from a button — use
MenuorTieredMenu. - Top-of-page navigation —
MenuBarorTabMenumatch that orientation.
Key UX patterns
- Section headers expand and collapse on click; the active route's parent stays expanded automatically so users see where they are.
- Persist expand / collapse state per session so users don't reopen the same section after every navigation.
Accessibility
- Headers are disclosure buttons (
aria-expanded) wrapping a realnav+ulof links — links remain in the document tab order so screen-reader users can browse without arrow-key acrobatics. - Indicate the active item with
aria-current="page"rather than colour alone, so the current location is announced.
Dock
When to use
- Dashboard / kiosk surfaces that mimic a desktop environment with a fixed cluster of app or workspace shortcuts.
- Niche product showcases where the macOS-style hover-magnify is part of the expected feel.
When not to use
- Standard product navigation —
MenuBar,TabMenu, orPanelMenucommunicate "navigation" far more clearly to first-time users. - Action overflow — use
MenuorSpeedDial; the dock metaphor is for persistent shortcuts, not transient actions.
Key UX patterns
- Each icon needs a tooltip / label — symbol-only docks fail the first-time user every time.
- Magnification on hover is visual flavour, not affordance; click targets must remain large enough at rest for touch and tremor users.
Accessibility
- Render as a
navlandmark orrole="toolbar"depending on the items — apps go in nav, single-click commands in toolbar; both need anaria-label. - Each icon button needs an accessible name (visible label or
aria-label); the magnification animation must be skipped underprefers-reduced-motion.
Dialog
When to use
- Confirming a destructive or irreversible action (delete account, discard changes, send to thousands).
- Completing a self-contained sub-flow (compose message, edit a record, paywall step) where surfacing it inline would crowd the page.
- Surfacing content too rich for a popover — forms with multiple fields, images-with-controls, multi-step wizards.
When not to use
- Simple yes / no confirmations attached to the trigger — use
ConfirmPopup; it stays anchored to the action and feels lighter. - Transient feedback ("Saved", "Copied") — use
Toastso the user isn't blocked from continuing. - Anything navigation-shaped: a route deserves a URL. Don't trap a whole page inside a dialog.
- Stacking dialogs from dialogs — second-level decisions belong inline or as the next step within the current dialog.
Key UX patterns
- Open in a transition (200–300ms scale + fade) so the user perceives the layer change without jarring; close mirrors it.
- Move focus into the dialog on open — usually the first interactive element or the close button — and trap focus inside while open.
- Restore focus to the trigger when the dialog closes; dismiss via Esc, backdrop click, and an explicit close button.
- Body scroll is locked while a dialog is open so the page doesn't drift behind it.
Accessibility
- Set
role="dialog"andaria-modal="true"; bindaria-labelledbyto the title andaria-describedbyto a short description if one exists. - Render the dialog via
Teleportto<body>so it lives outside any clipped / transformed ancestor — important for fixed positioning, z-index, and the focus trap. - Apply
aria-hidden="true"to the page content behind the dialog (or useinert) so AT users don't traverse into a section they can't currently reach.
Drawer
When to use
- Mobile navigation that hides off-canvas until summoned by a hamburger trigger.
- Filters, settings, or row-detail panels where the user wants to keep the underlying list visible.
When not to use
- Persistent navigation that should always be in view — use
Sidebar. - Focused, must-acknowledge tasks — use
Dialog; a drawer is rarely modal. - Quick supplementary previews tied to a trigger —
PopoverorOverlayPanelfeel lighter.
Key UX patterns
- Slide in / out (~250ms) from a declared edge; remember the edge so the same drawer always opens from the same place.
- Dismiss on Esc, on backdrop click (when modal), and via an explicit close button — three ways out is the ceiling, not the floor.
Accessibility
- When modal, follow the W3C dialog pattern:
role="dialog"+aria-modal="true"+ focus trap + pageinert; restore focus to the trigger on close. - Non-modal drawers don't trap focus but still need an accessible name (
aria-labelledby) and a close button reachable by keyboard.
Sidebar
When to use
- App layouts where primary navigation should stay visible alongside the working area (admin consoles, IDEs, dashboards).
- Reading layouts with a table of contents, filters, or peripheral metadata that benefit from staying on screen.
When not to use
- Mobile primary navigation — collapse into a
Drawer; a persistent sidebar eats too much viewport. - Focused, blocking tasks —
Dialogdemands attention; a sidebar invites browsing.
Key UX patterns
- Provide a collapse / expand control with iconified state on collapse so the sidebar can shrink without disappearing.
- Persist collapsed / expanded state per user — they shouldn't have to re-collapse it on every visit.
Accessibility
- Render as a
navorasidelandmark depending on content; navs get anaria-labellike "Primary". - The collapse toggle is a button with
aria-expanded+aria-controlspointing at the sidebar; collapsed icon-only items need accessible names.
ConfirmDialog
When to use
- Destructive or irreversible actions (delete account, wipe data, send to thousands) where interrupting the flow is exactly the point.
- Cross-cutting decisions where the user needs to read the consequence before answering — a dialog gives space for context.
When not to use
- Low-stakes confirmations attached to a specific control —
ConfirmPopupstays anchored and feels lighter. - Generic information / form panels — use
Dialog; ConfirmDialog is purpose-built for binary decisions. - Transient feedback ("Saved") — that's a
Toast, not a confirmation.
Key UX patterns
- Default focus on the safer action ("Cancel") for destructive operations so an accidental Enter doesn't fire the destruction.
- Label the buttons with the action verb ("Delete account") rather than generic Yes / No — users skim, and verbs survive skimming.
Accessibility
- Inherits the W3C dialog pattern:
role="alertdialog"when the message is critical (so AT users hear it without needing to traverse),aria-modal="true", focus trap, restore focus on close. - Bind
aria-labelledbyto the title andaria-describedbyto the message so screen readers announce both on open.
ConfirmPopup
When to use
- Low-stakes confirmations where keeping context (the row, the button) visible is helpful — "Delete this comment?", "Discard draft?".
- Bulk-action toolbars where the trigger and the question naturally belong on the same surface.
When not to use
- High-stakes / destructive operations that deserve full attention — escalate to
ConfirmDialog; the modal interruption is the whole point. - Confirmations that need a longer explanation, links, or a form — that's a
Dialog.
Key UX patterns
- Anchor to the trigger with flip-aware positioning so the popup never overflows; it should feel like a continuation of the click, not a new layer.
- Dismiss on Esc, on outside click, and after either button is pressed — avoid forcing a manual close on a single-question popup.
Accessibility
- Treat as a small, non-modal dialog — focus moves into the popup on open and returns to the trigger on close; Tab cycles between the buttons inside.
- Programmatically link to the trigger via
aria-controlsand togglearia-expanded; the message itself is announced via the dialog's accessible description.
OverlayPanel
When to use
- Inline detail or quick-edit affordances next to a row or chart — show a small form, a date picker, or a help block without leaving the page.
- Filter / sort builders that need their own controls but shouldn't displace the underlying data.
When not to use
- Plain action lists from a button — use
Menu; the menu role is what AT users expect. - One-line clarifications — that's a
Tooltipby spec; tooltips can't host interactive content. - Focus-blocking flows — promote to
Dialogwhen the user shouldn't be able to interact with the page behind.
Key UX patterns
- Position via a flip-aware engine; close on Esc, outside click, and (often) on focus moving outside the panel.
- Imperatively shown / hidden via a ref so triggers stay declarative — same pattern as
ContextMenuandConfirmPopup.
Accessibility
- Treat as a non-modal dialog: role
dialog, accessible name from the trigger or the panel's heading, focus moves in on open and back to the trigger on close. - The trigger gets
aria-haspopup="dialog"+aria-expanded; without these, the chevron / button looks like a no-op to AT users.
PopOver
When to use
- Glossary / definition pop-ups, profile cards on hover, inline calendars next to a date input — content that belongs to a specific anchor.
- Anywhere a
Tooltipisn't enough because the content has links, buttons, or formatting.
When not to use
- A short single-line clarification — use
Tooltip; popovers shouldn't open for a plain caption. - Action lists from a button — use
Menu/TieredMenu; the menu role is what's expected. - Page-blocking flows — promote to
Dialogwhen the user must complete the task before continuing.
Key UX patterns
- Two trigger styles: click-controlled (sticky until dismissed) and hover / focus (closes when both leave) — never blend the two for one popover.
- Position with collision detection so the popover flips to keep the anchor visible; an arrow pointing at the trigger reinforces ownership.
Accessibility
- Treat as a non-modal dialog:
role="dialog"(ortooltiponly if the content is non-interactive),aria-labelledby, focus enters on open for click-triggered popovers. - The trigger gets
aria-haspopup="dialog"andaria-expanded; popovers must remain reachable while the trigger is focused so keyboard users can read their own content.
ContextMenu
When to use
- File / table / canvas surfaces where users expect right-click to reveal item-scoped commands (Cut / Copy / Rename / Delete).
- Power-user flows that want to keep the toolbar uncluttered while still offering fast access to many actions.
When not to use
- The only path to a feature — context menus are discovery-hostile; mirror the actions in a visible
Menubutton or toolbar. - Mobile or touch-first surfaces without a right-click equivalent — long-press is acceptable but document it; don't rely on convention alone.
Key UX patterns
- Open at the pointer position with flip-aware placement so the menu never overflows; close on Esc, outside click, or after activating an item.
- Scope the items to the right-clicked target; if no target is selected, either disable item-scoped commands or hide them entirely.
Accessibility
- Right-click isn't keyboard-reachable — bind Shift+F10 and the dedicated context-menu key to open the same menu at the focused element, per the W3C menu pattern.
- Use
role="menu"+role="menuitem", with Up / Down to traverse, Enter to activate, Esc to close and restore focus to the originating element.
Right-click the box below (or call show() via the ref).
Right-click here
Tooltip
When to use
- A one-line clarification of an icon-only control (a settings cog, a magnifier-only search button).
- Surfacing a non-obvious affordance (keyboard shortcut, what a metric counts, exact timestamp behind a relative one).
When not to use
- Anything the user actually needs to read — tooltips are dismissable transient UI; don't hide critical instructions or errors there.
- Content longer than one short line, or content with its own actions — that's a
Popovercase. - As a substitute for a label. If a control has only a tooltip, keyboard-only users without hover lose discovery.
Key UX patterns
- Show on hover and on keyboard focus — never hover-only — and dismiss on Esc per WCAG 1.4.13 (Content on Hover or Focus).
- A short open delay (200–500ms) avoids accidental flashes during pointer travel; close should be near-instant.
- Position via a flip-aware library so the tooltip never overflows the viewport; prefer a placement that doesn't cover the trigger or the next likely target.
Accessibility
- Bind the trigger and the tooltip with
aria-describedby— the tooltip text becomes part of the trigger's accessible description without doubling as its name. - The tooltip itself must be reachable while the trigger is focused (no flicker on hover-out before the user reads it); WCAG 1.4.13 requires it stay visible until dismissed by Esc, hover-off, or focus moving away.
- Don't put interactive content inside a tooltip — by spec it's a description, not a container; use
Popoverwhen you need links or buttons inside.
Toast
When to use
- Confirming async actions ("Saved", "Copied to clipboard", "Email sent") where the user shouldn't be blocked.
- Surfacing background events with low urgency — sync completed, file uploaded, connection restored.
When not to use
- Errors the user must acknowledge or fix — promote to an inline
InlineMessagenext to the field, or toConfirmDialogif action is required. - Information the user actually needs to read carefully — toasts auto-dismiss; users who look away miss them entirely.
Key UX patterns
- Per WCAG 2.2.1 (Timing Adjustable): every toast is dismissable, pauses on hover / focus, and is configurable / extendable; never auto-close a toast that demands action.
- Stack new toasts in a fixed corner with a clear order; respect
prefers-reduced-motionby skipping the slide animation.
Accessibility
- Use
role="status"+aria-live="polite"for confirmations androle="alert"+aria-live="assertive"only for genuinely critical messages — over-using "alert" trains users to ignore it. - Render via
Teleportto<body>so toasts escape clipped ancestors; close buttons need accessible names ("Dismiss notification").
ScrollTop
When to use
- Long-form content (articles, docs, infinite-scroll feeds) where the back-to-top distance matters.
- Tables / lists that grow well past one viewport and lack a sticky header users can click to reset position.
When not to use
- Short pages — the button covers content for no benefit; gate it behind a scroll-distance threshold (e.g. 400px / one viewport).
- Pages with a sticky header that already includes home / top affordance — duplicating it adds noise.
Key UX patterns
- Appear only after the user scrolls past the threshold; fade in / out so it doesn't pop into view abruptly.
- Smooth-scroll back to top, but honour
prefers-reduced-motionby jumping instantly when the user prefers it.
Accessibility
- The control is an icon-only button — give it
aria-label="Scroll to top"; relying on the chevron alone fails screen-reader users. - After activation, move focus to the page's first heading or skip-link target so keyboard users actually arrive there, not just the visual viewport.
The real ScrollTop only appears after scrolling 400px. Below is the preview variant, always visible for inspection.