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, or ButtonText.
  • 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 ButtonPrimary as 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 ButtonPrimary instead.
  • 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 — ButtonText reads 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 ButtonPrimary for 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 Menu off a ButtonOutlined trigger; 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" and aria-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 Menu or 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-haspopup and aria-expanded reflecting 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 InputNumber for numbers, InputMask for fixed formats, Calendar for dates.
  • Binary or categorical choice — use Checkbox, RadioButton, or Dropdown instead 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 isInvalid state and an adjacent message explains why.
  • Helper text — when present — sits below the field with the same aria-describedby association as the error message.

Accessibility

  • Programmatic <label for> association is required; an aria-label only suffices when a visible label genuinely cannot be shown.
  • Set aria-invalid="true" on error and reference the message via aria-describedby.
  • Honour the OS auto-fill / autocomplete contract — set the right autocomplete token 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 Editor case, 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 isInvalid state 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-describedby contract as InputText.
  • 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-describedby pointing 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.50 vs 1.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 plain InputText with an inputmode hint; arithmetic semantics don't apply.
  • Categorical pick-from-set choices that are numbered — use RadioButton or Select.

Key UX patterns

  • Set min / max / step so 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.NumberFormat rather than a hand-rolled regex; users in nl-NL type 0,5 and 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 InputText with a placeholder.
  • 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-describedby contract as InputText; describe the expected format in the helper text or label, not only via the mask.
  • Set inputmode to 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" and autocomplete="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 MultiSelect or AutoComplete with multiple selection so the user picks instead of types.
  • Single-value entry — chips imply "you can add more"; use InputText when 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-describedby so 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 InputSwitch or ToggleSwitch; they read as switches, not pending form values.
  • Many options where users need search or virtualisation — use MultiSelect or ListBox.

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 / Select so 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-checked on 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 AutoComplete so 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-expanded on the trigger and role="option" with aria-selected on each item; aria-activedescendant tracks 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 Select with 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-label on 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 InputChips or AutoComplete with a "create new" branch.
  • Few options that always fit on screen — use a column of Checkbox rows; 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.

AutoComplete

When to use

  • Long lists where scrolling a Select is 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-controls wires trigger → panel; aria-activedescendant moves 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 Select or Dropdown; a tall always-open list dominates.
  • Boolean choice or 2–5 options — use Checkbox or RadioButton; 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" with role="option" children carrying aria-selected; aria-multiselectable on 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 ToggleButton per option, or just a row of Checkbox; SelectButton is single-select.
  • Six or more options — the row gets too wide and labels truncate; use Dropdown or Select.
  • 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" with aria-checked per segment; one tab stop, Arrow keys cycle.
  • The group needs an accessible name via aria-label or aria-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 InputSwitch or ToggleSwitch; 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-pressed with role="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 RadioButton or SelectButton.
  • Cases where toggling is destructive or hard to reverse (deactivate account, drop a backup) — confirm via Dialog instead; 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" with aria-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-visible ring.
  • 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 RadioButton or SelectButton.

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 ToggleSwitch by 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-checked contract as ToggleSwitch; Space toggles, Tab moves.
  • Pair with a programmatic label (<label for> or aria-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 SelectButton or RadioButton.
  • Wide ranges with too many possible values to feel each step — combine with an InputNumber for 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 step on 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" with aria-valuemin, aria-valuemax, aria-valuenow, and aria-valuetext when 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 / step explicitly; loop-around behaviour rarely matches expectations and surprises users.

Accessibility

  • Use role="slider" with aria-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" with aria-checked per star — so screen-reader users hear "3 of 5" and can pick via Arrow keys.
  • Provide an aria-label per 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 RadioButton or SelectButton; 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 API where 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 both aria-valuetext for 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 InputMask with 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-label with 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 Editor concern 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 contenteditable with a real role="textbox" and aria-multiline="true"; an unlabelled contenteditable div is invisible to assistive tech.
  • Toolbar buttons are real <button>s with aria-pressed reflecting current selection state — bold is on / off depending on cursor position.
  • Provide an accessible name on the editor wrapper ("Comment", "Article body") via aria-label or aria-labelledby tied 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 AutoComplete so 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" with aria-expanded / aria-level / aria-setsize / aria-posinset per 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.

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 AvatarGroup to 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 Tooltip showing the full name on hover when the avatar appears without an inline label.

Accessibility

  • Provide an alt describing the person ("Maria Doe"), not the asset ("profile photo"); decorative duplicates of an adjacent visible name should use alt="".
  • 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 Message or Toast.

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 ButtonText or Chip.

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 InputChips or AutoComplete with 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 button with aria-expanded reflecting state and aria-controls pointing 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 Card or Panel to avoid the fieldset/legend semantic 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:none without losing the announcement; use a visually-hidden technique if it must be off-screen.
Personal details

FieldSet groups related controls.

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, set aria-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 ProgressSpinner instead.
  • 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-motion overrides (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 to aria-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 the aria-busy state 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 width and height (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 alt for content images and alt="" 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.
Sample

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 Galleria or 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 slider role 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 — Image already 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 alt describing 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 DataTable reads faster — three numbers don't need a chart.
  • Single-metric KPIs — a stat card or MeterGroup is 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-label that 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 DataView to render rich item layouts (product catalogue, blog list).
  • Hierarchical data — use TreeTable when 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-sort on 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
AppleFruit12
BreadBakery5
MilkDairy8

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 Tree or TreeTable.

Key UX patterns

  • Provide both list and grid layouts and remember the user's choice across sessions.
  • Pair with Paginator for long sets, or virtualise via VirtualScroller for 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 MultiSelect or Listbox is 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 listbox with 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 — Tree is denser and keyboard-friendly for navigating folders or categories.
  • Hierarchy with per-node tabular attributes — TreeTable shows 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 TreeTable so columns sit alongside the structure.
  • Visualising a single org diagram top-down — OrganizationChart is the right shape.
  • Flat collections — a Listbox or DataTable is 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", child role="treeitem", aria-expanded on parent items, aria-selected on 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".
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 — Tree is 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 treegrid pattern: role="treegrid" with row-level aria-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.
NameSize
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 Chart with a sequential colour scale, not a discrete tone enum.
  • Hierarchical relationships — use Tree or TreeTable.

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-label composed 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.
ReadEditDeleteTransfer
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 MultiSelect with 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 listbox with 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 DataTable sorted by a date column is clearer.
  • Heavy comparative analysis across events — a Chart serves 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 decorative aria-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-size matching 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-setsize must 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.
Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9
Item 10

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 help command — 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 log live 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 InlineMessage next 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 and role="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 Message banner would dominate.

When not to use

  • Page- or region-level status — use Message so 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 isInvalid state — 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-describedby so 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.
Email is required.

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 ProgressSpinner so the user doesn't expect a percentage.
  • Static gauges of a fixed-value-within-a-range (disk used / quota) — use MeterGroup with role="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" with aria-valuenow / aria-valuemin / aria-valuemax; provide an aria-label describing what is progressing.
  • Updates should be polite — frequent aria-valuenow changes can swamp a screen reader; debounce announcements to roughly every 10%.
62%

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 ProgressBar so 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 Skeleton placeholders 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 an aria-label like "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 Badge or Tag reads 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" with aria-valuenow / aria-valuemin / aria-valuemax — distinct from progressbar, which implies forward motion.
  • Provide an aria-label per meter ("Disk used by media") and announce threshold-crossings as separate role="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 Dialog with 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 ProgressSpinner instead.

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 a role="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 set tabindex="-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 Dialog with 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 Tabs would 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-controls pointing to its panel), each panel uses role="region" with aria-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 TabMenu or 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 of role="tab" headers, each with aria-selected + aria-controls; panels use role="tabpanel" with aria-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 Drawer or Dialog conveys "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-size per 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", and aria-valuenow / aria-valuemin / aria-valuemax reflecting 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 VirtualScroller for 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 ButtonPrimary is 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 Divider between groups so the strip parses faster than as a flat row.
  • Use start / end slots 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 Tooltip per button; keep at least the primary action labelled in text.

Accessibility

  • Follow the W3C ARIA toolbar pattern: container has role="toolbar" with an aria-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 visible Tooltip alone 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 advertise aria-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 CorporateHeader or PromotionHeader.
  • 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-sidebar so the host wires the hamburger to its navigation drawer.
Console

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 CorporateFooter or PromotionFooter.
  • 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 TabMenu or Tabs; 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 ProgressBar instead 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 VirtualScroller or 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 nav landmark with a list of links — TabMenu is navigation, not the W3C tab pattern, so don't apply role="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, or MegaMenu if items expand into multi-column panels.
  • Items with their own submenus — promote to TieredMenu; flat Menu can'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 toggles aria-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 MegaMenu for multi-column reveal.
  • Section navigation that just switches views — TabMenu reads 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, and aria-haspopup + aria-expanded on 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 MenuBar or TabMenu is calmer and faster.
  • App-style menubars with command lists — use MenuBar for 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 real nav + ul markup 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 — Menu is lighter and avoids the chevron noise.
  • Persistent sidebar navigation — PanelMenu stays expanded inline rather than fanning out as overlays.
  • Top-level horizontal navigation — MenuBar or MegaMenu fit 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" and aria-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 Menu or TieredMenu.
  • Top-of-page navigation — MenuBar or TabMenu match 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 real nav + ul of 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, or PanelMenu communicate "navigation" far more clearly to first-time users.
  • Action overflow — use Menu or SpeedDial; 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 nav landmark or role="toolbar" depending on the items — apps go in nav, single-click commands in toolbar; both need an aria-label.
  • Each icon button needs an accessible name (visible label or aria-label); the magnification animation must be skipped under prefers-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 Toast so 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" and aria-modal="true"; bind aria-labelledby to the title and aria-describedby to a short description if one exists.
  • Render the dialog via Teleport to <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 use inert) 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 — Popover or OverlayPanel feel 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 + page inert; 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 — Dialog demands 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 nav or aside landmark depending on content; navs get an aria-label like "Primary".
  • The collapse toggle is a button with aria-expanded + aria-controls pointing 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 — ConfirmPopup stays 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-labelledby to the title and aria-describedby to 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-controls and toggle aria-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 Tooltip by spec; tooltips can't host interactive content.
  • Focus-blocking flows — promote to Dialog when 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 ContextMenu and ConfirmPopup.

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 Tooltip isn'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 Dialog when 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" (or tooltip only if the content is non-interactive), aria-labelledby, focus enters on open for click-triggered popovers.
  • The trigger gets aria-haspopup="dialog" and aria-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 Menu button 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 Popover case.
  • 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 Popover when 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 InlineMessage next to the field, or to ConfirmDialog if 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-motion by skipping the slide animation.

Accessibility

  • Use role="status" + aria-live="polite" for confirmations and role="alert" + aria-live="assertive" only for genuinely critical messages — over-using "alert" trains users to ignore it.
  • Render via Teleport to <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-motion by 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.