Consumer Design System · v1.0.0 · 2026
Built for products that billions of people touch.
A business-to-consumer design system: 90 component categories across ten domains — commerce, social, media, onboarding, growth, trust. Every entry carries a decision register, an accessibility contract, and a platform anchor. Humans copy the code below each preview; agents read the machine contracts. Same bytes, two doors.
Original, independent work. This system cites public platform guidelines — Apple Human Interface Guidelines, Material Design 3, WCAG 2.2, App Store Review Guidelines, Google Play policy, GDPR, COPPA, FTC rules — as design constraints, the way a building cites a building code. It is not affiliated with or endorsed by any platform. It is the consumer-market counterpart to the Institutional Finance Design System: same discipline, different physics — where the institutional system optimises for density and auditability, this one optimises for clarity, delight, and trust at scale.
The domain map
Counts are computed at runtime from the live navigation — they can never drift from the sidebar. Click a domain to filter.
What every entry carries
The entry contract
4-cell register · live preview · copyable code · machine contractRendered from the same tokens the code uses — light and dark, no screenshots.
The job the component exists for, stated so a reviewer can check it.
A system that can't say no isn't a system. Every entry names its misuse.
Focus, labels, target sizes, reduced motion — the behaviour that must survive a redesign.
A public guideline the entry answers to — HIG, Material 3, WCAG 2.2, store policy, GDPR, COPPA, FTC.
Design principles
Clarity before cleverness
Consumer attention is borrowed, not owned. Every screen answers "what is this, what can I do" in the first glance — decoration never competes with the answer.
Deference to content
The interface is the frame, not the painting. Glass, hairlines, and restraint keep the user's photos, products, and words as the loudest thing on screen.
Motion is physics, not garnish
One spring curve everywhere. Motion explains where things came from and where they went — and every animation respects prefers-reduced-motion as a contract, not a suggestion.
Touch first, then scale up
44px minimum targets, thumb-reach layouts, bottom sheets over centered dialogs on small screens. Desktop is the enhancement, not the default.
Trust is a feature
Consent is real, cancellation takes as few steps as signup, and growth components persuade without trapping. The FTC's word for the alternative is a rule violation; ours is a design failure.
One contract, two readers
Humans read the previews; agents read tokens.json and components.json — generated from this page, so the two can never disagree.
Start · Machine Layer
The system agents can read.
Two machine-readable files ship alongside this page. They are not documentation about the system — they are the system, in a shape a tool call can consume. The component index is generated from this page's own navigation, so it can never drift from what you're looking at.
Readable by whatever you already run
Claude
ChatGPT
Codex
Gemini
Antigravity
Cursor
Figma MCP
Grok
DeepSeek
Qwen
Hermes Agent
OpenClaw
The contracts are plain JSON over HTTPS — anything that can fetch a URL or speak MCP can consume them, cloud assistants and on-prem open-weight models alike. Logos identify third-party tools for context; no affiliation or endorsement is implied.
Connect it
No server, no install. Point any agent, script, or CI job at the two files — the component index carries every entry's id, domain, summary, platform anchor, and canonical URL.
The institutional sibling already ships this way — eds-mcp serves 65 contracts through 23 tools. These contracts share the same shape, so a cds-mcp server is a thin adapter over the same two files.
What a tool call sees
Honest scope. The two JSON files are real and downloadable today. The tool calls above are an illustrative console showing how these contracts back an MCP server — the pattern is proven in production by the author's eds-mcp (23 tools, 65 contracts) for the institutional system; a consumer-system server would serve these files the same way. Payload shapes here are simplified for reading.
Start · Changelog
Changelog
Versioned like software, because it is software. Historical entries are never edited — only appended.
- 90 component categories across 10 domains — foundations, core, navigation, onboarding & identity, commerce, social, media, growth & engagement, trust & safety, patterns.
- Every entry ships the four-cell decision register with a platform anchor (HIG, Material 3, WCAG 2.2, App Store / Play policy, GDPR, COPPA, FTC), a live tokenised preview, and copyable vanilla code.
- Machine layer: cds/tokens.json + cds/components.json, generated deterministically from this page's navigation; illustrative MCP console shows the tool-call shape.
- Dual theme (light default, dark), spring motion gated behind prefers-reduced-motion, 44px touch targets, WCAG 2.2 AA contrast baseline.
Foundations
Color
A near-neutral canvas with one working accent. Consumer color earns attention it doesn't demand: semantic colors mean the same thing on every screen, and no state is ever encoded by hue alone.
Accent for the one primary action per view and for selected states. Semantic green/red/amber only for outcome, danger, and caution. Everything else stays neutral so content owns the saturation.
Never use red for emphasis — it is reserved for destructive and error meaning. Never encode a state by color alone; pair with an icon, weight, or label (streaks, unread, live all double-encode here).
Text tokens hold ≥ 4.5:1 against their surfaces in both themes; large text and icons ≥ 3:1. Both themes ship from the same token names — components never reference a raw hex.
WCAG 2.2 · 1.4.3 contrast (minimum) and 1.4.1 use of color; HIG Color (semantic, adaptive colors); Material 3 color roles.
Neutrals & text
Accent & semantics
Foundations
Typography
Two voices: a tight display face for moments, the native system stack for everything else. Consumer type is generous — bigger minimums, looser leading, tighter tracking as size grows.
Display face (Inter Tight, −0.03em and tighter) only for hero and section moments. Body is the system stack at 16px minimum — it renders instantly, matches the OS, and never needs a font download to be readable.
No display face below 20px — tight tracking collapses at small sizes. Never justify body text, never letter-space lowercase body copy, never ship text in images.
All sizes in rem so user font-size preferences scale the whole system; layouts survive 200% zoom (WCAG 1.4.4) and reflow at 320px (1.4.10). Numerals in prices and counters are tabular.
HIG Typography (Dynamic Type); Material 3 type scale; WCAG 2.2 · 1.4.4 resize text, 1.4.10 reflow, 1.4.12 text spacing.
Interactive specimen
The scale
Foundations
Spacing & Layout
A 4px base grid with an 8-step scale. White space is the cheapest premium material there is — this system spends it deliberately.
Scale steps only — 4, 8, 12, 16, 24, 32, 48, 64. Related things sit one step apart, unrelated things two. Section rhythm comes from consistent vertical steps, not from dividers.
No off-scale values to "make it fit" — if a layout needs 13px, the layout is wrong. Don't add dividers and spacing; pick one separator per level.
Interactive targets keep ≥ 24px of clear space between them even when visuals are smaller (WCAG 2.5.8); list rows pad to a 44px minimum touch height.
HIG Layout (44pt targets, safe areas); Material 3 4dp grid; WCAG 2.2 · 2.5.8 target size (minimum).
Foundations
Materials & Elevation
Three levels of shadow and one glass. Elevation answers exactly one question — what is above what — so it is spent like money.
Level 1 for resting cards, level 2 for raised or hovering elements, level 3 only for overlays that float above the page (sheets, toasts, menus). Glass for chrome that must show content scrolling beneath it.
Glass never carries body text over unknown content without a contrast fallback. No stacked glass — one translucent layer per screen region, or blur costs melt low-end phones.
Every glass surface declares a solid fallback color for browsers without backdrop-filter, and text on it still meets 4.5:1 against the worst-case underlying content in the fallback.
HIG Materials (vibrancy, translucency); Material 3 elevation; WCAG 2.2 · 1.4.3 contrast over variable backgrounds.
Blur + saturation lifts the layer; the hairline sells the edge. Content stays the painting — this is just the frame.
Foundations
Motion
One spring, three durations. Motion in this system is spatial reasoning — it tells you where things came from and where they went. Anything that only decorates gets cut.
Enter/exit of overlays, state changes the user caused, and continuity between views. The spring — cubic-bezier(0.32, 0.72, 0, 1) — is shared by everything, so the product feels like one object.
No ambient looping animation near reading content. No motion longer than 480ms on the critical path — the user came to do something, not to watch the interface perform.
prefers-reduced-motion is a contract: transitions collapse to opacity or nothing, parallax and auto-playing movement stop. Nothing flashes more than three times per second, ever.
WCAG 2.2 · 2.3.3 animation from interactions, 2.3.1 flash thresholds; HIG Motion; Material 3 motion (emphasized easing).
Foundations
Iconography
Icons are labels that got shorter, not decoration that got meaning. One family, one optical grid, and a text label whenever the action is destructive or unfamiliar.
Recognition-level concepts only — search, share, settings, play. Filled variants mark selected state against outlined resting state (the tab bar pattern).
Icon-only buttons for irreversible actions are forbidden — "delete" gets a word. Never invent a metaphor a first-time user must decode; if it needs a tooltip to be understood, it needs a label instead.
Every functional icon carries an accessible name (aria-label or visually-hidden text); decorative icons carry aria-hidden. Minimum functional icon target: 44px including padding.
HIG SF Symbols conventions (weight-matched, filled-selected); Material Symbols; WCAG 2.2 · 1.1.1 non-text content.
Foundations
Haptics & Sound
The invisible foundation. A web page can't fire a Taptic Engine, so this entry is the policy layer: when native surfaces of the same product should speak through touch and sound, and when they must stay silent.
Haptics confirm what the eye might miss: a toggle landing, a pull-to-refresh arming, a payment succeeding. Sound is rarer still — reserved for messages arriving and capture moments.
Never haptic on scroll, on every keystroke, or to celebrate the app itself. Duplicate meaning, don't invent it — a buzz with no visible counterpart is noise with extra steps.
All haptic and audio feedback is redundant to a visible change — deaf and haptic-off users lose texture, never information. System silent-mode is always respected.
HIG Playing haptics ("use haptics consistently, don't overuse"); Material guidance on haptic feedback; WCAG 2.2 · 1.1 perceivable redundancy principle.
Core · Control
Button
The pill is the promise: one obvious next step per view. Everything else steps down in weight until hierarchy does the persuading, not color.
Actions — buy, send, save, continue. One primary per view region; secondary and ghost carry everything else. Full-width primaries live at the thumb edge on mobile.
Navigation is a link, not a button. Danger styling is for destructive actions only — never for urgency theatre ("Buy now before it's gone" does not get to be red).
44px minimum height. Loading sets aria-busy and disables the control — a double-tap on "Place order" is a duplicate charge. Focus ring is restyled, never removed.
HIG Buttons (prominence hierarchy, 44pt targets); Material 3 button hierarchy; WCAG 2.2 · 2.5.8 target size, 2.4.7 focus visible.
Core · Form
Text Input
Labels stay visible, errors say what to do next, and the keyboard that appears is the one the field deserves. Most abandoned checkouts die in a form.
Free-form single-line entry with a permanent label above and helper text below. The right inputmode and autocomplete attributes are part of the component, not an optimisation.
Placeholder-as-label fails the moment typing starts. If the answer is one of five things, use a select or segmented control — don't make thumbs spell what a tap can choose.
Error text is tied via aria-describedby and never conveyed by border color alone. Fields never clear on error. Labels are real <label> elements — tapping them focuses the field.
WCAG 2.2 · 3.3.1 error identification, 3.3.2 labels, 3.3.7 redundant entry, 1.3.5 identify input purpose (autocomplete); HIG Text fields.
Core · Form
Search Field
The front door of every catalog, feed, and library. Rounded, iconed, instant — and it never punishes an empty result with a dead end.
Whenever the collection exceeds what one screen can browse. Results update as-you-type where the corpus allows; recent searches fill the empty field's dropdown.
Don't ship search as an apology for bad navigation on a five-item menu. Never hide the only path to content behind an icon-only collapsed search on desktop.
type="search" with a visible clear affordance; role and results count announced to screen readers via a live region ("14 results"). Zero results always offers a next move.
HIG Search fields; Material 3 search; WCAG 2.2 · 4.1.3 status messages (result counts).
Core · Form
Select
The native picker wearing the system's clothes. On phones the OS wheel and sheet pickers beat anything a div can fake — so this select stays a <select>.
Five to fifteen mutually exclusive options. Below five, use a segmented control or radios so all choices are visible; above fifteen, use search-in-a-sheet.
Never for two options (that's a toggle or radio pair), never for navigation, and never a custom-div dropdown when the native element would do — you inherit a decade of OS accessibility for free.
Keyboard type-ahead, arrow navigation, and screen-reader announcement come from the native element and are never re-implemented worse. The label is external and permanent.
HIG Pickers; Material 3 menus; WCAG 2.2 · 4.1.2 name-role-value (free with native semantics).
Core · Control
Slider
For values where the feel matters more than the digits — volume, brightness, price range. The current value is always readable, never guessed from thumb position.
Continuous or dense ranges adjusted by feel. Pair with a live output element; for money and quantities offer direct numeric entry too.
Not for precise required values (a date of birth on a slider is a usability crime) and not with fewer than ~10 steps — that's a segmented control.
Native input[type=range]: arrow keys step, Home/End jump, aria-valuetext gives units ("$120"). The 26px thumb clears WCAG's 24px floor with a 44px touch halo.
HIG Sliders; Material 3 sliders; WCAG 2.2 · 2.5.7 dragging movements (keyboard alternative required), 2.5.8 target size.
Core · Control
Toggle Switch
The switch means now — flip it and the thing is on. If anything else must happen first (a save button, a payment), it isn't a switch.
Binary settings with immediate effect: notifications, dark mode, autoplay. The label states the thing controlled, not the state ("Wi-Fi", never "On").
Not in forms that submit later — that's a checkbox. Never pre-flip a switch that spends the user's money or shares their data; defaults are a consent question, not a growth lever.
Built on a real checkbox input — Space toggles, state announces. Color is not the only state signal: the thumb position carries meaning in grayscale.
HIG Toggles; Material 3 switch; WCAG 2.2 · 1.4.1 use of color, 4.1.2 name-role-value; GDPR Art 7 — data toggles default off.
Core · Form
Checkbox & Radio
The oldest controls on the web, kept native and kept honest: checkboxes for any-of, radios for one-of, and the whole row is the target.
Checkbox for independent opt-ins and multi-select lists; radio when exactly one of 2–5 visible options must be chosen and the choice should be comparable at a glance.
Pre-ticked marketing checkboxes are a consent violation, not a conversion tactic. Radios with one option are a lie; radios with twelve are a select.
Native inputs with accent-color — full keyboard and screen-reader behaviour for free. Labels are clickable; groups get a fieldset/legend so the question travels with the answers.
WCAG 2.2 · 1.3.1 info & relationships (fieldset), 2.5.8 target size; GDPR Art 7(2) — unbundled, unticked consent; ePrivacy consent conventions.
Core · Control
Segmented Control
All the options, all the time. The sliding thumb makes switching feel free — which is exactly when users explore.
2–5 short, mutually exclusive views or filters of the same content: List/Map, Weekly/Monthly, Delivery/Pickup. The selection changes the view immediately.
Not for actions (that's buttons) and not for navigation between unrelated destinations (that's tabs or a tab bar). Labels that truncate mean the control is the wrong choice.
Arrow keys move selection; the group carries a single tab stop. Selected state is encoded by the raised thumb and weight, not color alone.
HIG Segmented controls; Material 3 segmented buttons; WAI-ARIA radio-group pattern.
Core · Control
Quantity Stepper
Plus, minus, and a number that never surprises you. The humble stepper moves more units of physical goods than any carousel ever will.
Small integer adjustments with a sane range — cart quantities, tickets, guests. Bounds are enforced at the control: the minus disables at the minimum instead of erroring.
Not for large or unbounded numbers (use an input) and not for continuous values (use a slider). Removing the last item is a delete — hand off to a remove affordance, don't step to zero silently.
Buttons carry aria-labels ("Increase quantity"); the value is an output element announced on change. Both buttons hold the 44px target floor.
HIG Steppers; WCAG 2.2 · 4.1.3 status messages, 2.5.8 target size.
Core · Control
Chip & Tag
Small, tappable, and plural. Chips are how consumer products let people say "these, not those" without a form.
Filter sets, interest pickers, removable selections. Multi-select by default; a chip row scrolls horizontally on mobile rather than wrapping into a wall.
Chips are not buttons — they select, they don't submit. If only one may be active, show it as a segmented control or radio chips with explicit single-select semantics.
Toggle chips expose aria-pressed; removable chips make the ✕ its own labelled 24px+ target. Selection is encoded by fill inversion, not hue alone.
Material 3 chips (filter/input/assist); HIG Tokens & tags conventions; WCAG 2.2 · 2.5.8.
Core · Display
Badge & Count
Tiny, loud, and easy to abuse. A badge is a claim on attention — this system rations them like the currency they are.
Status that changes decisions (Shipped, Refunded, New) and unread counts that clear when acted on. A count badge must always be dismissible by doing the thing it points at.
Never manufacture urgency — a permanently red "3" that never clears trains users to ignore every badge you'll ever ship. Marketing does not get to borrow the notification badge.
Counts carry accessible text ("3 unread messages"), truncate at 99+, and pair color with position and text — status badges always contain a word, never only a color dot.
HIG notification badge conventions; Material 3 badges; WCAG 2.2 · 1.4.1 use of color.
Core · Display
Avatar & Presence
The pixel-sized proof there's a person here. Initials fall back gracefully, groups stack, and the green dot never lies.
Anywhere content has an author or a counterparty — posts, chats, reviews, orders ("packed by"). Deterministic gradient + initials when no photo exists, so the same user always looks the same.
Don't show presence unless it's real and consented — a fake "online" dot is a trust forfeiture. No avatars for system messages; robots get glyphs, not faces.
Images carry the person's name as alt text; presence pairs the dot with text nearby ("Active now"). Stacked groups expose a text count ("and 12 others").
WCAG 2.2 · 1.1.1 non-text content, 1.4.1 use of color; GDPR — presence is personal data, off by default where jurisdictions require.
Core · Display
Card
One idea per card, one card per idea. The hover lift is the system's handshake — content rises to meet you, gently.
Self-contained units in a browsable set — articles, places, playlists. The whole card is the link; inner actions stop propagation and get their own targets.
Not as a wrapper for everything — a settings screen of nested cards-in-cards is a spacing failure wearing borders. Sequential reading content wants a list, not a card grid.
One tab stop per card (the primary link), inner actions reachable after it. The lift transition is transform-only and disappears under reduced motion.
Material 3 cards; HIG content organisation; WCAG 2.2 · 2.4.4 link purpose in context.
Alpine mornings
Twelve cabins above the fog line, from $89 a night.
Focus mix
Two hours of instrumental momentum, refreshed weekly.
Core · Display
List & Cell
The inset grouped list is the load-bearing wall of every consumer app: settings, inboxes, results, orders. Icon, two lines, trailing detail — a grammar everyone already reads.
Homogeneous, vertically scanned items. The chevron means "more screen behind this"; a trailing control (switch, count) means the row is the whole story.
Don't mix row semantics in one group — navigation rows and toggle rows live in separate sections. Rich comparison shopping wants cards; lists are for scanning, not weighing.
Rows are ≥44px, entire row is the target, secondary text truncates rather than wraps at scan-breaking length. Semantics: a nav list is a list of links, not divs with click handlers.
HIG Lists and tables (inset grouped); Material 3 lists; WCAG 2.2 · 2.5.8, 1.3.1.
Core · Navigation-in-place
Tabs
Peer views of the same subject, underlined so the floor tells you where you stand. Tabs change the view; the tab bar (see Navigation) changes the world.
2–6 sibling facets of one thing — Posts / Replies / Media on a profile. State survives switching; nothing is lost by looking.
Not for sequential steps (that's a stepper flow) and not as an overflow bin — a "More" tab is information architecture giving up.
ARIA tabs pattern: arrow keys move, the tablist is one tab stop, selection follows the roving focus. The underline is position-encoded, not color-encoded.
WAI-ARIA tabs pattern; Material 3 tabs; HIG segmented alternatives guidance.
Core · Feedback
Toast & Snackbar
Confirmation that doesn't ask for anything back. Glass pill, four seconds, gone — and when it carries an Undo, it is quietly the most humane component in the system.
Outcomes that need acknowledgment but not action: saved, sent, added to bag. Pair destructive actions with an Undo toast instead of a pre-confirm dialog (see the Undo pattern).
Never for errors that require action — those need a banner or dialog that waits. Never stack three toasts deep; queue them. A toast with two buttons is a dialog in denial.
role="status" (polite announcement), pauses on hover/focus, and any action inside is reachable before timeout — WCAG gives users control over time limits, so Undo lingers.
Material 3 snackbar; WCAG 2.2 · 2.2.1 timing adjustable, 4.1.3 status messages.
Core · Overlay
Modal Dialog
The interface's interruption budget, spent one dialog at a time. If the user didn't cause it and can't lose anything, it doesn't get to be modal.
Decisions that block everything else: discard unsaved edits, confirm a payment change, sign in to continue. Title states the decision; buttons state outcomes, never "Yes/No".
Marketing does not get modals. Mobile prefers a bottom sheet for anything with options. And a modal on page load — before the user has done anything — is the fastest trust burn in the catalog.
Focus moves in, cycles inside, and returns to the trigger on close. Esc closes, scrim-click closes non-destructive dialogs, and the page behind is inert to screen readers.
WAI-ARIA dialog pattern; HIG Alerts (outcome-named buttons); WCAG 2.2 · 2.4.3 focus order, 2.1.2 no keyboard trap.
Core · Overlay
Bottom Sheet
The mobile-native answer to "show me options without leaving." It rises from where thumbs live, and the grabber is a promise: you can always push it back down.
Contextual actions, filters, and details on mobile — anything a desktop popover would do. Detents (half, full) let content and control share the screen.
Not for blocking decisions (that's a dialog — a sheet is dismissible by design) and not nested: a sheet opening a sheet means the flow wanted a full screen.
Swipe-down, scrim tap, and Esc all dismiss; the grabber is decorative while a real close button carries the accessible name. Focus management mirrors the dialog contract.
HIG Sheets (detents, grabber); Material 3 bottom sheets; WCAG 2.2 · 2.1.2, 2.5.7 (swipe has button alternative).
Options
Core · Overlay
Popover & Tooltip
Small truths anchored to the thing they describe. Tooltips name, popovers act — neither ever holds information that exists nowhere else.
Tooltip: naming icon controls on hover/focus, desktop only. Popover: a short menu of contextual actions anchored to a "…" trigger.
Tooltips don't exist on touch — anything a phone user needs cannot live in one. Errors never go in tooltips. A popover taller than half the screen is a sheet with the wrong name.
Tooltips appear on focus as well as hover, are dismissible with Esc, and stay hoverable (WCAG 1.4.13). Popover menus use the ARIA menu pattern with arrow-key navigation.
WCAG 2.2 · 1.4.13 content on hover or focus; WAI-ARIA menu & tooltip patterns; HIG Popovers.
Core · Feedback
Progress & Loading
Honesty about time. Determinate when the end is known, a spinner only when it's short, and a skeleton when there's a shape worth promising.
Bars for uploads and multi-step flows; rings for compact contexts; spinners for sub-2-second waits. Anything longer than ten seconds needs a cancel.
Never fake progress — a bar that crawls to 90% and parks is a lie the user physically feels. Don't spinner the whole page for one widget's fetch.
role="progressbar" with aria-valuenow; long operations announce completion via a live region. Spinners respect reduced motion by slowing, not vanishing.
HIG Progress indicators; Material 3 progress; WCAG 2.2 · 4.1.3 status messages.
Core · Feedback
Skeleton
A promise of shape. The screen commits to its layout before the data arrives, so loading feels like focus resolving rather than construction work.
Content with a predictable silhouette — feed items, cards, profiles — loading in 0.5–5 seconds. Skeleton geometry must match the real content, or the layout shift double-charges the wait.
Not for instant loads (flash of skeleton is worse than nothing) and not for unpredictable content where a spinner is more honest.
The container carries aria-busy="true" and a text status ("Loading feed"); the shimmer is decorative, aria-hidden, and static under reduced motion.
Material loading guidance; WCAG 2.2 · 2.3.3 animation from interactions; Core Web Vitals CLS discipline.
Alpine mornings
Twelve cabins above the fog line, from $89 a night.
Core · Navigation-in-place
Pagination & Dots
Numbered pages for destinations you'll cite and return to; dots for carousels you'll drift through. Both tell you where you are — that's the entire job.
Pagination where position matters — search results, order history. Page dots for short swipeable sets (≤6 slides), always paired with swipe.
Feeds don't paginate (see Infinite Scroll pattern for the trade). Dots beyond six are noise — show a count ("3 / 12") instead.
nav landmark with aria-label="Pagination"; the current page has aria-current="page". Dots are buttons with slide names, not empty spans.
HIG Page controls; WCAG 2.2 · 2.4.8 location, 4.1.2 name-role-value.
Core · Display
Accordion
Progressive disclosure on native semantics. Built on <details>, so it works with JavaScript disabled, and searchable-in-page in modern browsers.
FAQs, size guides, shipping details — secondary content most people skip and some people need. Multiple panels may be open; users manage their own attention.
Never hide critical-path content (price, total, cancellation terms) behind a fold. Auto-closing siblings punishes comparison — leave panels where users put them.
<summary> is natively focusable and toggles with Enter/Space; the chevron flips as a state signal in addition to the disclosure itself.
WAI-ARIA disclosure pattern (native <details>); WCAG 2.2 · 4.1.2; HIG disclosure controls.
What's your return policy?
Do you ship internationally?
How do I change my order?
Core · State
Empty State
The first screen most new users actually see. An empty state is onboarding wearing casual clothes — it names the value and hands over the first move.
First-run, cleared inbox, no results. Formula: what this space is for, why it's empty, one action that fills it. Zero-results always keeps the query editable and suggests a loosened filter.
Never a bare "No data". Never a dead end after a search — that's where shoppers leave. Don't guilt-trip ("It's lonely in here…") — charm, don't manipulate.
The heading is a real heading in the page outline; the illustration is decorative and aria-hidden; the action is a first-class button, not a text link whisper.
Material empty state guidance; NN/g empty-state conventions; WCAG 2.2 · 2.4.6 headings and labels.
Nothing saved yet
Tap the heart on anything you like and it lands here — synced across your devices.
Core · Feedback
Banner & Inline Alert
Persistent context that waits. Where a toast taps you on the shoulder and leaves, a banner sits down until the situation changes.
Conditions that remain true: payment method expiring, feature in beta, order delayed. Inline alerts attach to the thing they describe — a form error banner lives above the form it indicts.
Not for one-off confirmations (toast) and not stacked three deep — if everything is urgent, nothing is. Dismissible only if the condition is genuinely ignorable.
Errors use role="alert" (assertive), the rest role="status". Icon + word + color triple-encode severity; the dismiss button names what it dismisses.
Material 3 banners; WCAG 2.2 · 4.1.3 status messages, 1.4.1 use of color.
Navigation
Tab Bar
Five doors at the bottom of the world. The tab bar is the most valuable real estate in a consumer app — every slot must earn daily use.
3–5 top-level, equally important destinations on mobile. Filled icon marks the active tab; each tab keeps its own navigation stack so switching never loses your place.
Never as an action bar — "Post" in slot three that opens a composer instead of a destination breaks the spatial model (if you do it, make it visually distinct and modal). Two tabs is a segmented control; six is a redesign.
Labels always visible — icon-only tab bars fail recognition tests everywhere they've been tried. Badges carry accessible counts. Safe-area padding is part of the component.
HIG Tab bars; Material 3 navigation bar (label conventions); WCAG 2.2 · 2.4.8.
Navigation
Drawer & Side Menu
The overflow home for secondary destinations — account, orders, help, legal. A drawer is where things go so the tab bar doesn't have to lie about their importance.
Secondary and account-level destinations, or the primary nav of dense multi-section products on tablet and desktop. Grouped, labelled, with the user's identity anchored at top.
Not as the only path to daily-use destinations on a phone — burying "Search" in a hamburger costs engagement measurably. Don't mirror the tab bar inside the drawer; duplicates confuse the map.
Opens as a modal region on mobile (focus trapped, Esc closes, scrim tap closes); on desktop it's a persistent nav landmark. The trigger announces expanded state.
Material 3 navigation drawer; WCAG 2.2 · 2.1.2 no keyboard trap, 4.1.2.
Navigation
Breadcrumb
The paper trail through a deep catalog. Mostly a desktop commerce citizen — on phones the back button carries this weight.
Hierarchies three levels or deeper — Home → Women → Shoes → Sneakers. Every crumb but the last is a real link; the last names the current page and links nowhere.
Not on flat products, not on mobile where it wraps into noise, and never as a step indicator in checkout — that's a progress stepper with different semantics.
nav[aria-label="Breadcrumb"] with an ordered list; the current page carries aria-current="page". Separators are decorative and hidden from screen readers.
WAI-ARIA breadcrumb pattern; schema.org BreadcrumbList (SEO); WCAG 2.2 · 2.4.8.
Navigation
Floating Action Button
One verb, promoted above the page. The FAB works when the screen has an obvious main thing to do — compose, add, capture — and fails everywhere else.
The single most common creative action of a screen: new message, add item, start workout. One per screen, thumb corner, persistent through scroll (or politely shrinking).
No FAB for destructive, rare, or ambiguous actions. Two FABs is zero FABs. If the action belongs to one list item rather than the screen, it belongs on the item.
56px target with a text label for screen readers ("Compose message"); never overlaps the last list item — scroll containers pad their bottom by the FAB's height.
Material 3 FAB; WCAG 2.2 · 2.5.8 target size, 2.4.7 focus visible.
Navigation
Large Title Header
The title starts as a headline and condenses into chrome as you scroll — orientation you feel rather than read. Scroll the demo.
Root screens of each tab — the moment of arrival. The large title gives the screen a name worth a headline; the condensed bar keeps it while content takes over.
Not on detail screens deep in a stack (content should lead) and not with titles longer than two words — "Order history & receipts" was never meant to be 34px.
The h1 exists once (the large form); the condensed bar mirrors it aria-hidden. The collapse is scroll-driven positioning, not animation — reduced motion changes nothing because nothing plays.
HIG Navigation bars (large titles); WCAG 2.2 · 1.3.1 heading structure.
Library
Onboarding & Identity
Welcome Flow
Three screens, one promise each, and a skip that actually works. The best onboarding is mostly the product, sooner.
First launch only, ≤3 screens, each selling one outcome (not one feature). Progress dots show the end is near; the last screen lands on the primary action.
Never gate the product behind a tour a user can't skip. Don't ask for permissions here (see Permission Primer) and don't ask for registration before showing any value.
Skip is visible from screen one, tab-reachable first. Swipe advances but buttons exist for every gesture (WCAG 2.5.7). Seen-state persists — nobody sees the tour twice.
HIG Onboarding ("get to the app quickly"); Material onboarding guidance; WCAG 2.2 · 2.5.7 dragging movements.
Find your next favorite
Recommendations that learn what you love — not what advertisers wish you loved.
Onboarding & Identity
Sign-in & SSO
The fewest keystrokes between a person and their account. Platform sign-in first, email as the dignified fallback, passwords as the last resort they've become.
Offer the platform's native sign-in alongside any third-party login, then email. Order by user benefit, not by your data appetite.
Don't demand an account before demonstrating value — browse first, sign in to keep. Never dark-pattern the email option into 8px grey text to juice SSO adoption.
Buttons follow each provider's brand rules (their guidelines are contractual); autocomplete="email" and "current-password" keep managers working; errors never say which of email/password was wrong.
App Store Review Guideline 4.8 (offering an equivalent privacy-respecting login option); provider brand guidelines; WCAG 2.2 · 3.3.8 accessible authentication.
Onboarding & Identity
One-Time Code
Six boxes and thirty anxious seconds. Everything about this component exists to make the code arrive, paste, and verify itself.
Verifying possession of an email or phone. Auto-advance per digit, paste fills all six, auto-submit on the last — the user should never hunt for a Verify button.
Don't use OTP as the only factor for high-value changes (SIM-swap is real); pair with passkeys where it matters. Resend appears after a countdown, not never.
autocomplete="one-time-code" lets the OS offer the code from SMS; inputs are labelled "Digit 1 of 6"; WCAG 3.3.8: no transcription puzzle — paste always works.
WCAG 2.2 · 3.3.8 accessible authentication (minimum); HIG security code AutoFill conventions.
Onboarding & Identity
Passkey Prompt
The end of passwords, sold in one card. The design job is trust: say what a passkey is in one sentence, and make "not now" a real choice.
Right after a successful password or OTP sign-in — the moment the user has just felt the pain the passkey removes. One sentence of what, one of where it syncs.
Not during first-run before any sign-in exists, and never as a repeating nag — decline twice, stay quiet until the user visits security settings themselves.
"Not now" is same-size, adjacent, honest. The biometric icon row is decorative; the copy never claims biometrics leave the device — because they don't.
FIDO2 / WebAuthn UX conventions; HIG passkeys guidance; WCAG 2.2 · 3.3.8 (passkeys are the compliant path).
Sign in with your face or fingerprint
A passkey replaces your password with the way you already unlock this device. It syncs through your device account and never leaves your hardware unencrypted.
Onboarding & Identity
Profile Setup
Ask little, explain why, skip everything. Every optional field completed here is a gift the user gave you — design like you know it.
After account creation, one screen, only what the product needs on day one. Each request carries its reason inline ("your name appears on reviews").
No birthday "for your special offer", no gender without a product reason, no phone number as a growth hook. Data you can't justify inline is data you shouldn't collect.
Everything after the display name is skippable and says so. Photo upload has a keyboard path and never requires a camera. Progress persists — abandoning costs nothing.
GDPR Art 5(1)(c) data minimisation; App Store Guideline 5.1.1 (don't require unneeded personal data); WCAG 2.2 · 3.3.2.
Onboarding & Identity
Permission Primer
You get one shot at the OS permission dialog. The primer spends its own screen to make sure you take that shot at the moment the user already wants to say yes.
Immediately before a system permission request, triggered by a user action that needs it — asking for location when they tap "Find stores near me", not at launch.
Never chain three permission asks at first launch. Never re-prompt after a decline with the same pitch — respect the no, offer settings later when the feature is touched again.
The primer states exactly what is accessed, when, and what saying no costs. "Not now" keeps the app fully usable — degraded gracefully, never punitively.
HIG "request permission at the moment of need"; Play policy on prominent disclosure; GDPR Art 6 purpose limitation.
See stores near you?
We use your location only while you're using the app to sort stores by distance. It's never stored or shared.
Onboarding & Identity
Consent Manager
The component regulators read first. Reject is as easy as accept, defaults are off, and the toggles do what they say — consent here is a designed artifact, not a legal speed bump.
First visit where consent-based processing applies, and permanently reachable from settings and footer. Purposes are separated, written in plain language, and individually controllable.
No "Accept all" in bold against "Manage options" in grey whisper — asymmetric prominence is the textbook dark pattern EU regulators cite. No consent walls for content that doesn't require it.
"Reject all" and "Accept all" are visual equals on the first layer. Non-essential toggles default off. Withdrawing is one screen away forever (GDPR: as easy to withdraw as to give).
GDPR Art 7 & Recital 32 (freely given, specific, unambiguous); ePrivacy; CCPA/CPRA opt-out rights; EU DSA Art 25 (no deceptive interface design).
Onboarding & Identity
Paywall & Plans
The most scrutinised screen in any subscription app — by users, by app review, by the FTC. This one converts by being legible: real prices, real terms, restore always visible.
At the moment of premium intent — tapping a locked feature — not as an ambush at launch. Plans show total billed price and period; the trial states when it converts and how to cancel.
No fake countdown timers, no pre-selected annual masquerading as monthly pricing, no hidden ✕. "Free trial" that means "we bill you in 72 hours unless you remember" gets rejected — by review and by users.
Close is visible from render. Price per period appears exactly as billed ("$59.99/year", not "$4.99/mo billed annually" alone). Restore purchases and terms are reachable without scrolling tricks.
App Store Review Guideline 3.1.2 (subscription clarity) & Schedule 2 rules; Play subscriptions policy; FTC Negative Option Rule (clear disclosure before billing).
Listen without limits
Offline downloads · lossless audio · no ads
Billed to your store account. Cancel in Settings → Subscriptions in two taps. Restore purchases · Terms
Commerce
Product Card
The unit of browsing. Image leads, price never hides, and the save affordance floats where thumbs already are.
Grid and carousel browsing. Image, name, one line of context, price, rating — nothing else survives the cut. The card links to detail; only save acts inline.
Never bury the price to "get them to detail" — hidden prices read as expensive. No fake "3 left!" scarcity unless inventory is genuinely tracked and true.
One link per card with a name that includes the product ("Ceramic pour-over set, $48"); the wishlist button is a separate labelled target announcing its toggled state.
WCAG 2.2 · 2.4.4 link purpose; FTC truth-in-advertising (scarcity claims must be true); schema.org Product markup.
Ceramic pour-over set
Hand-thrown · 4 colors
Trail runners
Recycled knit · true to size
Commerce
Price Display
Tabular, truthful, and complete. A price is a fact with typography — strikethroughs need a real former price, and the number you show is the number they pay.
Tabular numerals everywhere money appears so digits align in lists. Compare-at prices show savings as both amount and percentage; unit prices accompany multi-size goods.
No drip pricing — fees revealed at the last step are the single most-cited dark pattern in FTC enforcement. No fictitious strikethrough anchors that were never charged.
Strikethrough prices carry accessible context ("was $64, now $48") — <s> alone is silent on most screen readers. Currency symbols travel with the number, locale-formatted.
FTC Guides Against Deceptive Pricing (16 CFR 233); EU Price Indication Directive (prior-price rules); WCAG 2.2 · 1.3.1.
Commerce
Cart Line Item
Where second thoughts happen. Every line item makes changing your mind cheap — adjust, save for later, remove — because a trapped cart is an abandoned cart.
Cart and order review. Thumbnail, name, variant, unit price, quantity stepper, line total. Remove is one tap plus an undo toast — not a confirm dialog.
Don't inject upsells between line items — cross-sell lives below the list. Never auto-add items (warranty, insurance) the user must notice and remove; that's an FTC unfairness case with a design file.
Quantity changes announce the new line total politely. Remove names its item ("Remove trail runners"). Price changes since adding are flagged, not silently swapped.
FTC Section 5 unfairness (pre-checked add-ons); WCAG 2.2 · 4.1.3; Baymard cart usability conventions.
Commerce
Checkout Summary
No math surprises. Every fee has a name, the total is the largest number on screen, and it matches the charge to the cent.
The right rail (desktop) or pre-pay screen (mobile) of checkout. Subtotal, shipping, tax, discounts, total — each row aligned, tabular, updated live as options change.
Never a "service fee" that appears only here — junk fees at the summary stage are the FTC's current enforcement centerpiece. Never collapse the breakdown behind a tap on mobile.
Live updates announce via a polite region ("Total updated: $53.24"). The pay button repeats the total — the last thing read before commitment is the number.
FTC junk-fee rulemaking (all-in pricing); EU Consumer Rights Directive Art 6 (total cost pre-order); WCAG 2.2 · 4.1.3.
Commerce
Payment Method
Radio rows for money. Wallets first (they convert best and typo least), saved cards next, new card last — and the selected row is unmistakable.
Checkout and settings. Each method shows its brand, its last four, and its expiry state. One is always selected; the default is the user's last successful method.
Never preselect a store credit line or BNPL the user didn't choose — payment defaults are a consent surface. Expired cards can't be selected, only fixed.
A real radio group: label reads "Visa ending 4242, expires 12/27". Adding a card uses autocomplete cc-attributes and never blocks paste.
PCI-DSS display rules (last four only); card-network brand guidelines; WCAG 2.2 · 3.3.8 (no card-number memory tests).
Commerce
Promo Code
A tiny form with outsized feelings. Success celebrates with the exact amount saved; failure explains without blaming.
Checkout, collapsed behind "Have a code?" so full-price buyers aren't invited to leave and hunt coupons. Applies without page reload; the summary updates in the same breath.
Don't auto-apply a worse code over a better one. Expired codes say expired — "invalid code" for a code that worked yesterday teaches distrust.
Uppercases as-you-type (display only), trims whitespace on paste, result announced via status region. The applied state shows the code and its effect together, removable in one tap.
WCAG 2.2 · 3.3.1, 3.3.3 error suggestion, 4.1.3; FTC advertised-discount truthfulness.
Commerce
Ratings & Reviews
Social proof with a chain of custody. Stars summarise, distributions tell the truth, and "verified purchase" means the system checked.
Star input for collection (tap or drag, 1–5), star display plus count everywhere the product appears. Reviews sort by helpfulness with recency available.
Suppressing or reordering negative reviews violates the FTC's review rules — display practices are a legal surface now. No incentivised reviews without disclosure.
Display stars carry text ("4.6 out of 5, 1,942 ratings"); the input is keyboard-operable radios styled as stars. Half-star precision displays, never collects.
FTC Rule on Consumer Reviews and Testimonials (16 CFR 465); Consumer Review Fairness Act; WCAG 2.2 · 1.3.1.
Survived a week of rain in Portland and still looks new. Runs slightly large — I sized down and it's perfect.
Commerce
Save & Wishlist
The lowest-commitment yes in commerce. A save is a bookmark on desire — instant, reversible, synced, and never guilt-tripped.
A heart on every product surface, toggling instantly with optimistic UI and a toast pointing at the list. Saved items remember price at save time so drops can be shown truthfully.
Saving must not require sign-in to feel instant — save locally, merge on auth. Never spam "items in your wishlist are selling fast!" pushes without explicit opt-in to that exact category.
The heart is a toggle button with aria-pressed and a name including the product; the fill change pairs with the pressed state so color isn't the only signal.
WCAG 2.2 · 4.1.2 name-role-value; GDPR-conscious local-first storage; HIG feedback conventions.
Commerce
Order Tracking
The screen people open eleven times for one package. Its whole job is a truthful answer to "where is it and when" — everything else is decoration.
Post-purchase. Timeline of real states with timestamps, current state emphasized, delivery window as a range that narrows honestly as confidence grows.
No fake precision — "arriving 2:14 pm" from a model that knows ±3 hours erodes every future promise. A stuck state acknowledges being stuck and offers support before the user hunts for it.
The timeline is an ordered list; each step names its state and time. Status color is tripled with icons and text. Updates announce politely if the screen is open.
FTC Mail Order Rule (shipment timing representations); WCAG 2.2 · 1.3.1, 1.4.1.
Social
Feed Post
The atom of the social web: who, what, and three verbs. The layout is a contract with the reader — authorship always above content, actions always below it.
Any authored unit in a scrolling feed. Avatar + name + time anchor trust; the action row keeps to three or four verbs with live counts.
Sponsored posts wear the same anatomy plus an unmissable "Sponsored" label — camouflaged ads violate FTC endorsement rules and reader trust in the same move.
The post is an article with the author as its heading; action counts are part of each button's name ("Like, 1,204 likes"). Media carries author-provided alt text — the composer asks for it.
FTC Endorsement Guides (ad disclosure); WCAG 2.2 · 1.1.1, 4.1.2; EU DSA transparency for recommender systems.
Social
Comment Thread
Conversation with indentation as grammar. One level of nesting reads as reply; two reads as argument; three reads as nothing at all — so this system stops at one.
Discussion under any post or product. Bubble carries author + text; the meta row underneath holds time, like, reply. Deeper threads collapse behind "View 12 replies".
Don't autoplay into hostile territory — sort by "most liked" hides brigading better than "newest". Don't let the reply box push content; it stays docked.
Semantic list structure preserves thread relationships for screen readers ("reply to Ana"). Collapsed counts are buttons, not links-that-lie. Report is reachable on every comment.
WCAG 2.2 · 1.3.1; EU DSA Art 16 (notice mechanisms adjacent to content); community-safety conventions.
Right? Empty at 6am, chaos by 9.
Social
Reaction Bar
Six feelings on a glass shelf. The long-press reveal is one of the small delights consumer software is allowed — spent here because reacting is frequent, safe, and reversible.
Quick emotional response on posts and messages. Five or six options maximum — the palette is a vocabulary and vocabularies need to be memorisable.
Not for decisions or feedback with consequences (that's a survey or report). Don't A/B swap the emoji set weekly; meanings accrete through stability.
Long-press has a click/keyboard path (the bar opens from a labelled button too — WCAG 2.5.7). Each emoji button carries its name ("Love"). Selection announces and dismisses.
WCAG 2.2 · 2.5.7 dragging/press alternatives; WAI-ARIA toolbar pattern; HIG feedback conventions.
Social
Chat Bubbles
Two voices, two sides, zero ambiguity. Yours right and tinted, theirs left and neutral — a convention so strong that breaking it reads as a bug.
1:1 and small-group messaging. Consecutive messages group under one timestamp; delivery state (sent, delivered, read) lives once at the thread's end, not on every bubble.
Read receipts are consent-gated and mutual — showing "read" to senders while hiding the setting is a dark pattern. Support bots must be visibly non-human.
The transcript is a log region; new messages announce without stealing focus. Bubble color carries ≥4.5:1 text; the typing indicator stills under reduced motion.
WCAG 2.2 · 4.1.3, 1.4.3; EU AI Act transparency (bot disclosure); messaging privacy norms (GDPR).
Social
Notification Item
A sentence with a face. Actor, verb, object, time — and unread state you can see from across the room. The list is an inbox, so it behaves like one.
The in-app notification center. Group by day, batch same-actor events ("Ana and 3 others liked…"), and deep-link to the exact object, never the home screen.
Marketing does not wear the notification costume — "Someone might have viewed your profile 👀" as engagement bait poisons the channel that ships real information.
Unread is background + dot + bolded text (triple-encoded); items mark read on visit and support mark-all. Each row is one link with a complete sentence as its name.
HIG Notifications ("valuable, not promotional"); Play FCM policy; WCAG 2.2 · 1.4.1.
Ana Reyes and 3 others liked your photo.
12 min agoTomas K. replied: "That stretch past the pier…"
1h agoMaya R. started following you.
YesterdaySocial
Follow Button
The smallest social contract. Following is loud, followed is quiet — the state change is the entire design, so it must be unmistakable and reversible in one tap.
Profiles, suggestions, discovery cards. Unfollowed is filled and inviting; following turns quiet secondary with the label changed — never just a color swap.
No confirm dialog on unfollow (it's reversible; asking makes it feel surveilled). Never follow anyone automatically — imported contacts get suggestions, not silent edges.
aria-pressed carries the state; the label text changes (Follow ↔ Following) so screen readers and grayscale users read the same truth. Optimistic update, quiet rollback on failure.
WCAG 2.2 · 4.1.2, 1.4.1; platform anti-spam norms (no auto-follow).
Social
Story Ring
A gradient halo that means "new, and it expires." The seen state — gray, quiet — is just as much the design as the rainbow.
Ephemeral content rails. Ring = unseen, gray = seen, no ring = no story. Your own slot leads with an explicit + affordance for creation.
Don't reset seen-state to farm taps — users notice, and the ring stops meaning anything. Don't put non-expiring content in the expiring costume.
Each story is a button: "Ana's story, unseen, 2 hours ago". Seen state is encoded by ring treatment plus order (unseen first), not color alone. The rail is keyboard-scrollable.
WCAG 2.2 · 1.4.1, 2.1.1; ephemeral-content honesty conventions.
Media
Video Player Controls
Controls that get out of the way and come back when called. Everything essential within one thumb arc: play, scrub, captions, volume.
Any owned playback surface. Controls fade after 3s of playback, return on tap/hover/focus. The scrubber shows buffered vs played; ±10s skips beat frame-precise dragging on touch.
Never autoplay with sound — muted autoplay with visible captions is the honest compromise. Don't hide captions behind a settings submenu; CC is a first-class control.
Full keyboard map (space, arrows, C for captions, F for fullscreen); controls are real buttons with names; fading controls never fade while focused (WCAG 2.1.1, 1.4.13).
WCAG 2.2 · 1.2.2 captions, 2.2.2 pause/stop/hide; HIG playback; Media Session API conventions.
Media
Mini Player
Playback that follows you around the app without blocking it. A glass bar above the tab bar: art, title, play, next — and a tap expands to the full stage.
Continuous audio/video while browsing elsewhere. Persistent across navigation, dismissible by swipe with a real stop — dismissing pauses, it doesn't secretly keep playing.
Not for content the user hasn't started. It never covers the tab bar or the last row of content — layouts reserve its height when active.
The bar is a region ("Now playing"); controls are named; the expand affordance is a real button as well as a tap surface. Swipe-dismiss has a button equivalent.
HIG now-playing conventions; WCAG 2.2 · 2.5.7, 4.1.2.
Media
Gallery & Lightbox
A grid that promises and a lightbox that delivers. The grid is uniform and dense; the lightbox is the one place chrome fully yields to the image.
Photo sets — products, listings, albums. Square grid crops for rhythm; the lightbox shows true aspect with pinch/double-tap zoom and swipe between images.
Don't lightbox single inline images that are already big enough. Don't trap scroll — reaching the end of a lightbox swipe returns to the grid, not to the first image forever.
Grid items are buttons naming their image; the lightbox is a dialog with arrow-key navigation, Esc close, and position announced ("Photo 3 of 8"). Zoom always has button + keyboard paths.
WCAG 2.2 · 2.5.7, 1.1.1; HIG image viewing conventions.
Media
Carousel
Scroll-snap, native momentum, and a peek of the next slide doing the affordance work. The browser already knows how to swipe — this component just stops fighting it.
Browsable peers where order is soft — featured collections, related items. The 82% slide width leaks the next card: visible continuation beats any arrow.
Never auto-rotate a carousel of content people read — rotation steals the thing they were reading. Critical content doesn't go in slide 3; nobody sees slide 3.
Native scroll means keyboard and screen-reader access come free; slides are list items with names. Any autoplay (hero promos only) pauses on hover, focus, and reduced motion.
WCAG 2.2 · 2.2.2 pause/stop/hide; CSS scroll-snap conventions; NN/g carousel research.
Media
Live Badge
Two words that must never lie: LIVE means now, and the pulse means the signal is real. The moment the stream ends, the badge dies with it.
Actually-live streams and events, on thumbnails and in players, paired with a viewer count when it's meaningful. Red, uppercase, pulsing dot — the convention is universal; keep it.
Never on premieres, reruns, or "live-style" drops — "LIVE" on recorded content is a trust forfeiture regulators call deceptive. Ended streams switch to "Streamed 2h ago" instantly.
The badge is text, not just color ("LIVE"), announced as part of the title. The pulse is decorative and stops under reduced motion without losing the label.
FTC deception standard (liveness claims); WCAG 2.2 · 1.4.1, 2.3.3.
Media
Captions
Most video is watched silent; captions are the soundtrack most people actually use. High contrast, two lines, and never covering the faces they transcribe.
All published video. Default on when the device is muted. Position bottom-center, lifting above controls when they're visible.
Auto-generated captions without a correction path aren't accessibility, they're liability diffusion — label them auto-generated and let creators fix them.
User-adjustable size and background opacity, honoring OS caption preferences where exposed. ≥4.5:1 against any video via scrim. Max two lines, ~42 chars each.
WCAG 2.2 · 1.2.2 captions (prerecorded), 1.2.4 (live); FCC caption quality rules; CVAA.
Growth · dark-pattern-free by construction
Onboarding Checklist
Progress you can feel. The checklist turns "set up your account" from a lecture into a game the user is already winning — because the first item arrives pre-checked.
Activation journeys with 3–5 real steps toward the product's core value. The endowed-progress head start (one item done at signup) is honest: they did create the account.
Steps that serve you, not them — "enable notifications" and "invite 3 friends" are not setup, they're acquisition wearing a checklist. Completion never gates paid-for functionality.
Progress announced on change ("2 of 4 complete"); dismissible for good once completed or explicitly hidden; each item deep-links to the actual task.
Endowed progress research (Nunes & Drèze); WCAG 2.2 · 4.1.3; EU DSA Art 25 (no manipulative pressure).
Growth · dark-pattern-free by construction
Streak & Achievement
Celebration without hostage-taking. A streak marks showing up; this one is designed so missing a day stings less than the habit is worth.
Genuine practice loops — learning, fitness, journaling — where consistency serves the user's own goal. Streak freezes and grace days are part of the component, not a paid add-on.
Not on consumption ("7-day shopping streak" serves nobody but the funnel). No guilt copy on breaks — "your streak is hurt 😢" notifications at 11pm are the pattern this entry exists to forbid.
The count carries text ("14-day streak, 5 of 7 days this week"); flame color pairs with the number. Celebration animation is one-shot and reduced-motion-gated.
EU DSA Art 25; FTC dark-pattern report (guilt-driven engagement); WCAG 2.2 · 2.3.3.
Life happens — you have 2 streak freezes, applied automatically.
Growth · dark-pattern-free by construction
Referral Card
Both sides of the deal on one card. Referrals work when the invitation is a gift, not an extraction — so the friend's reward leads.
After a moment of earned delight (first great order, milestone hit). State both rewards, the code, and the terms link — the whole deal, no asterisk archaeology.
Never auto-import and mass-message contacts; every invite is user-sent. No fake "3 friends joined!" social proof. Referral rewards that expire in 48 hours are pressure, not generosity.
Copy confirms via toast and status region; the code is selectable text, not an image; share uses the OS sheet with a clean pre-filled message the user can edit.
FTC endorsement rules (incentivised referrals disclose); CAN-SPAM / TCPA (user-initiated sends); WCAG 2.2 · 1.4.5.
Give $15, get $15
Friends get $15 off their first order. Once they order, you get $15 in credit. Full terms
Growth · dark-pattern-free by construction
Push Pre-Prompt
Permission with a menu. Instead of "allow notifications?", the primer asks which — orders only, or the social noise too — and the OS prompt fires only after a real yes.
At the first moment a notification would have helped ("your order shipped" beats any launch-time ask). Category toggles first, OS prompt second.
Never at first launch before value exists. Never re-prompt on a schedule. And the category promises are binding — routing marketing through the "order updates" channel is the lie that gets apps muted.
"Not now" is prominent and final until the user visits settings. Chosen categories map to real notification channels the OS exposes (Android) or the app respects (iOS).
HIG asking permission; Android notification channels; Play FCM policy; ePrivacy (marketing push is consent-based).
Growth · dark-pattern-free by construction
In-App Review Prompt
Ask happy people, once, at the right moment — and never intercept the unhappy into a private complaint box while herding fans to the store. That fork is now a violation, not a growth hack.
After a success the user caused (finished workout, delivered order), rate-limited far below the OS caps. Use the platform's native review API — it handles the UI and the throttling.
No sentiment-gating ("Enjoying the app?" → happy to store, unhappy to a form). The FTC's review rule bans review suppression; Apple and Google both prohibit review gating explicitly.
The prompt is dismissible, never repeats within the cooldown, and never blocks a task in progress. Feedback and rating are separate, both always reachable from settings.
FTC 16 CFR 465.4 (review suppression); App Store Guideline 1.1.7 & SKStoreReviewController limits; Play in-app review API rules.
Enjoying it? Tell the store.
A rating takes two taps and genuinely helps. Having trouble instead? Talk to support — both paths are right here, no quiz first.
Growth · dark-pattern-free by construction
Survey & NPS
One question, honestly asked, instantly dismissible. Survey fatigue is a design failure — this component is rationed like the interruption it is.
A single 0–10 or emoji-scale question after a completed experience, inline or as a quiet card — never a modal over a task. Follow-up text is optional and says so.
Not during first-run (nothing to rate yet), not after failures (measure that from support volume, don't make the wounded fill forms), and never twice in one quarter for the same person.
The scale is a labelled radio group with keyboard flow; endpoints carry meaning text. Submitting acknowledges once, quietly. Declining is remembered.
WCAG 2.2 · 1.3.1, 4.1.3; research-ethics conventions (voluntary, no dark nudges).
Growth · dark-pattern-free by construction
What's New
Release notes people might actually read: three items, verbs first, each row deep-linking to the feature it announces. Shown once, then it's a page, not a prompt.
Meaningful user-facing changes after an update — new abilities, not refactors. One sheet per release maximum, dismissible instantly, revisitable from settings.
"Bug fixes and improvements" doesn't earn an interruption. Neither do upsells wearing a changelog costume — a "what's new" that's three premium pitches is an ad with a version number.
Standard sheet semantics (dismissible, focus-managed); each item is a real link; the seen-flag is per-version and survives reinstalls via account state.
HIG onboarding & release-note conventions; WCAG 2.2 · 2.4.4.
Trust & Safety
Report & Block
The exits from a bad interaction, always two taps away. Report feeds the system; block protects the person — the component never confuses whose problem each solves.
On every piece of user-generated content and every profile, via the overflow menu. Reporting asks one clarifying question, confirms receipt, and says what happens next.
Don't bury it four screens deep to keep report volume flattering. Block never requires a reason — protection is not a form. Neither action notifies the reported party of who acted.
Block takes effect immediately and optimistically — the content vanishes now, the network catches up. Both flows are fully keyboard-operable and confirm via status region.
EU DSA Art 16 (notice and action) & Art 20 (appeals); App Store Guideline 1.2 (UGC apps must have report/block); Play UGC policy.
Trust & Safety
Content Warning
A blur that respects both the viewer and the content. It names the category, offers the choice, and remembers the preference — informed consent at feed speed.
Sensitive-but-allowed media: graphic news, medical content, spoilers. The veil names the category specifically ("Graphic injury") so the choice is informed, not a dare.
Not as an engagement mechanic — mystery-blurring benign content to farm taps teaches users to ignore real warnings. Policy-violating content is removed, not veiled.
The reveal is an explicit button ("Show anyway"); the choice can persist per category in settings. The warning text is readable at full contrast on the blur.
EU DSA systemic-risk mitigation; App Store 1.2 / Play sensitive-content rules; WCAG 2.2 · 1.4.3.
This photo from a news story shows a surgical scene.
Trust & Safety
Age Gate
A door, not a decoration. Where children's privacy law applies, the gate is designed to be neutral — it doesn't wink, hint, or make the underage path retryable until it passes.
Before collecting personal data where COPPA/GDPR-K applies, and before age-restricted content or purchases. Ask once, neutrally ("When were you born?"), without stating the passing age.
No "you must be 18, enter your age 😉" framing that coaches the answer. Failing users aren't offered a back-button retry loop — the session remembers.
Neutral field order (year first coaches less), no data retained from failed gates beyond the session flag, and the under-age experience degrades kindly, never mocks.
COPPA · 16 CFR Part 312 (neutral age screening, FTC guidance); GDPR Art 8; UK Age Appropriate Design Code.
When were you born?
We use this to set up the right experience. It's not shown on your profile.
Trust & Safety
Privacy Dashboard
Every promise the consent screen made, auditable in one place. What's collected, why, and the switch to stop it — the settings page as a trust document.
One screen from settings root. Each row: the data category, its plain-language purpose, its current state, and its control. Changes take effect immediately and confirm.
No euphemisms — "personalisation data" that means location history gets called location history. Controls that only pretend (off = still collected, just hidden) are the deepest dark pattern there is.
Rows are grouped by data type with real switches; every state change announces. The page itself never requires consent to view.
GDPR Arts 13–15 (transparency & access); CCPA right to know; Apple privacy nutrition labels (App Store 5.1).
Trust & Safety
Data Rights
Download everything, delete everything — as self-service buttons, not support tickets. The delete flow states its consequences once, plainly, then does what it says.
Account settings, one level deep. Export produces a machine-readable archive with an ETA. Deletion explains scope, offers a cooling-off window, and confirms completion out-of-band.
No retention theatre — five "are you sure" screens, surprise discount offers, and a mandatory phone call are the FTC's canonical obstruction examples. One honest confirm is the ceiling.
The typed-confirm (for irreversibility) accepts paste and screen-reader input; deadlines and grace periods are stated in dates, not "soon".
GDPR Art 17 (erasure) & 20 (portability); CCPA deletion right; App Store 5.1.1(v) — account deletion in-app if creation is in-app.
Trust & Safety
Verified Badge
A checkmark is a claim the platform makes with its own reputation. This one means identity was checked — never that content is true, and never that a fee was paid unless the label says so.
Next to display names of identity-verified accounts, consistently everywhere the name appears. Tapping it explains exactly what was verified and when.
If verification is purchasable, the affordance must distinguish paid from vetted — one glyph for two meanings collapses both. Never auto-verify advertisers.
The badge has a text name ("Verified account") in the accessible label of the name it decorates — not a mystery blue blob. It's never separable from the name in truncation.
EU DSA Art 25 & trader traceability (Art 30); FTC deception standard; WCAG 2.2 · 1.1.1.
Trust & Safety
Security Checkup
An annual physical for the account, readable in ten seconds. Green means done, amber means one tap to fix — and the score never shames, it invites.
Reachable from settings; surfaced gently after security events (new device, password change). Each row pairs a state with its one-tap remedy.
Never cry wolf — "your account is at risk!" for an unused promo email address burns the channel needed for real alarms. Upsells (VPN, premium monitoring) don't wear the security costume.
States triple-encode (icon + word + color); every fix lands on the exact screen that fixes it; completed items confirm and re-verify on return.
NIST 800-63 authenticator guidance; WCAG 2.2 · 1.4.1; platform account-security conventions.
Patterns
Pull to Refresh
The gesture that taught a generation how feeds work. Pull, arm, release — with a visible threshold so the hand always knows whether letting go will act.
Time-ordered content the user plausibly wants fresher — feeds, inboxes, scores. The arrow flips at the arming threshold; a spinner holds until real content lands.
Not on content that doesn't change (settings) and never as the only refresh path — a visible refresh control exists for keyboards, switch access, and everyone the gesture excludes.
WCAG 2.5.7: single-pointer gesture with a button alternative. Completion announces ("Feed updated, 4 new posts"). Rubber-banding respects reduced motion.
HIG refresh content controls; WCAG 2.2 · 2.5.7 dragging movements, 4.1.3.
Patterns
Infinite Scroll vs Pages
Infinite scroll is a loan against the user's sense of time. This system spends it only where content is truly endless — and always installs a footer the scroll can land on.
Entertainment feeds with no meaningful end. Load-more happens ahead of the viewport; position restores perfectly on back-navigation — losing your place is the cardinal sin.
Goal-directed contexts — search results, order history, anything comparable — get pagination or "Load more" buttons: they preserve place, footer, and the feeling of progress. The footer must be reachable; content that flees the scrollbar fails WCAG and patience alike.
New batches announce count, focus is never stolen, and a "Back to top" affordance appears after two screens. Session-length nudges ("you're all caught up") are honest stopping points, not shame.
EU DSA Art 25 & recital 67 (attention-exploiting design); WCAG 2.2 · 2.4.1; NN/g infinite-scroll research.
Patterns
Undo, Not Confirm
Confirmation dialogs make everyone pay a tax because someone might err. Undo charges only the error, only when it happens — act immediately, offer the way back.
Reversible destructive actions: delete a message, archive a thread, remove from cart. The action executes optimistically; a toast holds the undo for ~6 seconds; the model really can restore.
Truly irreversible or high-stakes actions (delete account, send money, publish to everyone) keep a real confirm — undo is for the reversible, and pretending otherwise is worse than the dialog.
The undo control is focusable before timeout, the timer pauses on hover/focus, and the same action is also available in an activity/trash surface for the slow.
WCAG 2.2 · 2.2.1 timing adjustable, 3.3.4 error prevention; HIG undo conventions; NN/g forgiveness principle.
Patterns
Error Recovery
Errors are moments of maximum attention — the one time you're guaranteed a careful reader. Say what happened, what it cost, and what to do, in that order.
Every failure surface. The formula: plain-language what ("Couldn't send"), the stakes ("your message is saved as a draft"), the action ("Try again"). Input is never thrown away.
No error codes as headlines (keep them small, for support), no blame ("you entered an invalid…"), and no dead ends — every error screen has at least one button.
role="alert" for blocking failures; retry buttons debounce; after two failed retries the surface changes shape and offers support instead of hope.
WCAG 2.2 · 3.3.1, 3.3.3; NN/g error-message guidelines.
Couldn't upload your photo
The connection dropped mid-upload. Your edit is saved on this device — nothing was lost.
Error 1006 — mention this to support if it keeps happening.
Patterns
Offline Mode
The network is a feature the world sometimes turns off. Offline design keeps reading, queues writing, and tells the truth about both.
A quiet banner when connectivity drops; cached content stays readable and marked fresh-as-of; outgoing actions queue visibly ("will send when online") instead of failing.
Don't block the whole app behind a full-screen dinosaur when 90% of it is cached and useful. Don't retry forever silently — surface the queue so users can cancel stale intents.
The state change announces once (not per-scroll); queued items are inspectable; reconnection syncs and reports ("2 messages sent"). Timestamps mark cached content honestly.
HIG & Material offline guidance; WCAG 2.2 · 4.1.3; PWA offline-first conventions.
Patterns
Cancellation Flow
The truest test of a product's ethics: leaving must be as easy as arriving. One path, two screens, zero guilt — the FTC calls it click-to-cancel; this system calls it the default.
Subscriptions and memberships. Cancel lives where billing lives, works in-app end-to-end, states what's lost and when, and confirms in writing. One save-offer maximum, skippable in the same tap.
No retention mazes: chat-agent-only cancellation, guilt copy ("we'll miss you 😢" over a shrunken cancel link), surprise pause-instead defaults, or five-screen offer gauntlets. Each is an enforcement citation with a UI attached.
The path is discoverable by search ("cancel"), completable by keyboard, and ends in a dated confirmation ("Access until Aug 12, no further charges"). Resubscribing is equally easy — symmetry both ways.
FTC Negative Option Rule (click-to-cancel); App Store 3.1.2 / Play subscription cancellation rules; EU Consumer Rights Directive.
Cancel your membership?
You'll keep full access until Aug 12. After that, no further charges. Your playlists and history stay saved for 12 months in case you return.
Patterns
Search & Filter
Query, refine, count — the loop behind every marketplace. Filters are chips you can see and remove one at a time; the result count updates with every change so cause never loses its effect.
Catalogs beyond one screen. Applied filters render as removable chips above results; a filter sheet batches complex refinement behind one "Show 142 results" commit button that always shows its count.
Never auto-apply filters the user didn't pick ("sponsored first" is a sort disclosure, not a default to hide). Zero results with active filters offers to loosen them, never a dead end.
Count changes announce politely; each chip's remove is a named target; "Clear all" appears at two or more filters. Filter state lives in the URL — shareable, restorable, honest.
WCAG 2.2 · 4.1.3, 2.5.8; EU DSA Art 27 (ranking transparency); Baymard filtering conventions.
That stretch past the pier is my favorite mile in the city.