Introduction
A single source of truth for design decisions, reusable patterns, and token-driven components.
This documentation site covers the foundational layers of the WV Design System: brand primitives, semantic aliases, mapped component tokens, and responsive typography and spacing. Every visual decision flows from a structured token architecture that supports light and dark modes across desktop, tablet, and mobile breakpoints.
Token Architecture
The system is built on four collections, each with a strict dependency chain:
- Brand/Value — Raw primitives (hex colors, spacing scale, font sizes). No semantic meaning.
- Alias — Semantic intent layer. Maps brand values to roles like Primary, Secondary, Neutral, Information, Success, Error.
- Mapped (light/dark) — The only layer components consume. Provides Surface, Text, Border, and Icon tokens for each theme mode.
- Responsive (Desktop/Mobile/Tablet) — Typography scales, jumper spacing, grid settings, and fixed padding per breakpoint.
Getting Started
Browse the sidebar to explore token definitions, component documentation, reusable patterns, and system-level principles. Every component page follows a consistent structure: overview, accessibility, construction specs, variants, usage guidelines, and code examples.
/* Reference a mapped token in CSS */
.card {
background: var(--surface-default-container);
border: 1px solid var(--border-default);
border-radius: var(--radius-m);
padding: var(--padding-l);
color: var(--text-body);
}
Color Tokens
Color is organized into brand primitives, semantic aliases, and mapped tokens for light and dark modes.
Primary (Orange)
Secondary (Blue)
Neutral (Gray)
Status Colors
Information (Status Blue)
Success (Status Green)
Error (Status Red)
Mapped Surface Tokens
Global
Surface tokens for page backgrounds, layout containers, action states, and brand colors under Surface/Global/.
Status
Surface tokens for status feedback under Surface/Status/.
Button
Surface tokens for button backgrounds under Surface/Button/.
Secondary
Surface tokens for secondary-branded areas under Surface/Secondary/.
Mapped Text Tokens
Default
Text color tokens under Text/Default/. Action tokens (previously in a separate Action sub-group) are now part of Default.
Status
Text color tokens for status messaging under Text/Status/.
Button
Text color tokens for buttons under Text/Button/.
Mapped Border Tokens
Global
Border color tokens under Border/Global/.
Status
Border color tokens for status feedback under Border/Status/.
Button
Border color tokens for buttons under Border/Button/.
Width
Border width tokens under Border/Width/. Same values for light and dark modes.
| Token | Reference | Value |
|---|---|---|
Width/action | {Border Width.m} | 2px |
Width/default | {Border Width.s} | 1px |
Mapped Icon Tokens
Default
Icon color tokens under Icon/Default/. Action tokens (previously in a separate Action sub-group) are now part of Default.
Status
Icon color tokens for status feedback under Icon/Status/.
Button
Icon color tokens for buttons under Icon/Button/.
Brand
Brand icon color tokens under Icon/Brand/.
Shadow Colors (Brand Primitives)
Alpha-channel shadow color primitives derived from the gray scale. These are used by Mapped shadow tokens for light and dark mode elevation effects. Values pending final verification from Figma.
| Token | Base Gray | Alpha | Hex |
|---|---|---|---|
shadow/0-a0 | Black | 0% | #00000000 |
shadow/200-a10 | Gray 200 | 10% | #d0d0d01a |
shadow/300-a10 | Gray 300 | 10% | #b3b3b31a |
shadow/400-a10 | Gray 400 | 10% | #9797971a |
shadow/1100-a10 | Gray 1100 | 10% | #1818181a |
shadow/1100-a50 | Gray 1100 | 50% | #18181880 |
Mapped Shadow Tokens
Semantic shadow color tokens for elevation effects. Each token has a light and dark mode value referencing Brand shadow primitives.
| Token | Light Mode | Dark Mode | Usage |
|---|---|---|---|
Shadow/Button | {shadow.200-a10} | {shadow.1100-a10} | Button drop shadows |
Shadow/L | {shadow.1100-a50} | {shadow.1100-a50} | Large/prominent shadows |
Shadow/M Default container | {shadow.300-a10} | {shadow.1100-a10} | Default container shadows (cards, snackbars) |
Spacing Tokens
A consistent spacing scale derived from the brand scale. Values follow a roughly doubling progression for the first 100, then increments of 8px.
Base Scale
Scale Reference
| Application | Variable Name | Multiplier | Value (px) |
|---|---|---|---|
| Container width | 9200 | 92 | 736 |
| Container width | 5600 | 56 | 448 |
| Container width | 4200 | 42 | 336 |
| Scale | 2400 | 24 | 192 |
| Scale | 2200 | 22 | 176 |
| Scale | 1400 | 14 | 112 |
| Scale | 1300 | 13 | 104 |
| Scale | 1200 | 12 | 96 |
| Scale | 1100 | 11 | 88 |
| Scale | 1000 | 10 | 80 |
| Scale | 900 | 9 | 72 |
| Scale | 800 | 8 | 64 |
| Scale | 700 | 7 | 56 |
| Scale | 600 | 6 | 48 |
| Scale | 500 | 5 | 40 |
| Scale | 400 | 4 | 32 |
| Line height & Padding only | 350 | 3.5 | 28 |
| Scale | 300 | 3 | 24 |
| Font size only | 250 | 2.5 | 20 |
| Base font size only | 225 | 2.25 | 18 |
| Scale | 200 | 2 | 16 |
| Font size (nav) only | 175 | 1.75 | 14 |
| Font size (legal) & Padding only | 150 | 1.5 | 12 |
| Scale | 100 | 1 | 8 |
| Padding and Corner radius only | 50 | .5 | 4 |
| Corner radius & Stroke only | 25 | .25 | 2 |
| Corner radius & Stroke only | 10 | .10 | 1 |
| Scale | 0 | 0 | 0 |
Jumper Tokens (Desktop / Tablet / Mobile)
Jumper tokens are responsive spacing values that shrink across breakpoints.
| Token | Desktop | Tablet | Mobile |
|---|---|---|---|
XL-L | 96px | 80px | 64px |
L-M | 64px | 56px | 48px |
M-S | 48px | 40px | 32px |
S-XS | 32px | 32px | 24px |
XS-XXS | 24px | 24px | 16px |
XXS-XXXS | 16px | 16px | 8px |
XXXS-Tiny | 8px | 8px | 4px |
Tiny | 4px | 4px | 2px |
Fixed Padding Tokens
Fixed padding tokens are static spacing values that remain constant across all breakpoints. They are used for internal component spacing such as gaps, padding, and margins.
| Token | Value | Scale Reference |
|---|---|---|
XXS | 2px | {scale.25} |
XS | 4px | {scale.50} |
S | 8px | {scale.100} |
M-S | 12px | {scale.150} |
M | 16px | {scale.200} |
L | 24px | {scale.300} |
XL | 32px | {scale.400} |
Grid Margins
Page margins are composed of a Global Margin (the outer gutter) and a Col Margin (the outermost column's margin). On a fixed layout, the total side margin is Global Margin + Col Margin. On a fluid layout, the margin is Global Margin only, allowing content to extend to the column edges.
| Token | Desktop | Tablet | Mobile |
|---|---|---|---|
Grid / Global Margin | 176px | 80px | 16px |
Grid / Col Margin | 16px | 16px | 8px |
Total fixed margin (per side) | 192px | 96px | 24px |
Usage
.section {
padding-top: var(--jumper-L-M);
padding-bottom: var(--jumper-M-S);
margin-left: var(--grid-global-margin);
margin-right: var(--grid-global-margin);
gap: var(--space-300);
}
Corner Radius (Mapped)
Component-specific corner radius tokens in the Mapped collection. These reference Alias Corner Radius values and are identical across light and dark modes. Card radius values pending Figma verification.
| Token | Reference | Resolved Value | Usage |
|---|---|---|---|
Corner Radius/M button | {Corner Radius.xxl} | 48px | Buttons (pill shape) |
Corner Radius/M field | {Corner Radius.m} | 8px | Input fields |
Corner Radius/S checkbox | {Corner Radius.s} | 4px | Checkboxes |
Corner Radius/S alerts | {Corner Radius.s} | 4px | Alert containers |
Corner Radius/XL round | {scale.1300} | 104px | Fully round elements |
Corner Radius/Button | {Corner Radius.xxl} | 48px | Button components |
Corner Radius/Field | {Corner Radius.m} | 8px | Form field components |
Corner Radius/Alert | {Corner Radius.s} | 4px | Alert component |
Corner Radius/S card | {Corner Radius.s} | 4px | Small cards |
Corner Radius/M card | {Corner Radius.m} | 8px | Medium cards |
Corner Radius/L card | {Corner Radius.l} | 16px | Large cards |
Corner Radius/Round | {scale.1300} | 104px | Fully round |
Typography Tokens
All type is set in Lato. Icons use Font Awesome 6 Free. Body M (16px) is the baseline reading size. Recommended max-width for body text is 60-75 characters per line for readability.
Overview
To maintain consistency with the grid and spacing increments, the same scaling increments are used for the type scale, with few exceptions, for ease of readability. Medium is the default or baseline size for any context.
Typographic Scale
| Group | Name | Multiplier | Size (px) | Line Height (px) |
|---|---|---|---|---|
| Headings | Hero | 8 | 64 | 76 |
| Headings | H1 XXL | 7 | 56 | 64 |
| Headings | H2 XL | 6 | 48 | 56 |
| Headings | H3 L | 5 | 40 | 48 |
| Headings | H4 M | 4 | 32 | 40 |
| Headings | H5 S | 3 | 24 | 28 |
| Headings | H6 XS | 2.5 | 20 | 24 |
| Body | Body XXL | 4 | 32 | 44 |
| Body | Body XL | 3 | 24 | 40 |
| Body | Body L | 2.5 | 20 | 32 |
| Body | Body L Subhead | 2.5 | 20 | 32 |
| Body | Body M | 2 | 16 | 26 |
| Body | Body S | 1.75 | 14 | 20 |
| Body | Body XS | 1.5 | 12 | 16 |
Heading Scale (Desktop)
Body Scale (Desktop)
Heading + Body Pairings (Mobile)
Each heading size pairs with a corresponding body size for visual hierarchy. Mobile heading sizes are smaller than their desktop counterparts.
Heading + Body Pairings (Desktop)
Desktop heading sizes scale up from mobile. The paired body sizes also shift accordingly.
Text Link Scale
Text links inherit the body size at their corresponding scale but use a heavier weight.
Responsive Comparison
| Style | Desktop (size / line) | Mobile (size / line) |
|---|---|---|
| Hero | 64 / 76 | 40 / 48 |
| H1 XXL | 56 / 64 | 32 / 40 |
| H2 XL | 48 / 56 | 28 / 36 |
| H3 L | 40 / 48 | 24 / 32 |
| H4 M | 32 / 40 | 20 / 28 |
| H5 S | 24 / 28 | 18 / 24 |
| H6 XS | 20 / 24 | 16 / 20 |
| Body XXL | 32 / 44 | 24 / 36 |
| Body XL | 24 / 40 | 20 / 32 |
| Body L | 20 / 32 | 20 / 32 |
| Body L Subhead | 20 / 32 | 20 / 32 |
| Body M | 16 / 26 | 16 / 26 |
| Body S | 14 / 20 | 14 / 20 |
| Body XS | 12 / 16 | 12 / 16 |
Font Weight Reference
Font weights are stored as numeric values in both the Brand primitives and Responsive collection. Composite typography definitions reference these via Responsive variable paths.
| Weight Name | Numeric Value | Used In |
|---|---|---|
| Regular | 400 | Body L, Body M, Body S, Body XS, Notification Alert Body, Notification Snackbar Body |
| Bold | 700 | H6 XS, Body L Subhead, XS Link, S Link, M Link, Notification Alert Heading, Notification Snackbar Heading |
| Black | 900 | Hero, H1-H5, Body XXL, Body XL |
| Solid | 900 | FA6 icons (S icons, M icons, L icons, Notification Snackbar Icon) |
Letter Spacing Reference
| Value | Applied To |
|---|---|
| -2% | Hero |
| -1.25% | Body XXL |
| -1% | H1 XXL |
| -0.5% | H2 XL, H6 XS |
| 0% | H3 L, H4 M, H5 S, Body XL, Body L, Body S |
| 1% | Body M, Body XS, XS Link, M Link |
Type Scale: Light vs Dark
The type scale uses the same sizes and weights in both modes. Only the text and background colors change between light and dark themes.
Body Copy Comparison
Heading Scale Comparison
Notification Typography
Component-specific typography tokens for Alert and Snackbar notification components. These values are consistent across Desktop, Mobile, and Tablet breakpoints.
Alert
| Element | Font Size | Line Height | Font Weight |
|---|---|---|---|
| Heading | 14px | 20px | 700 (Bold) |
| Body | 14px | 18px | 400 (Regular) |
| Icon | 16px | 20px | - |
Snackbar
| Element | Font Size | Line Height | Font Weight |
|---|---|---|---|
| Heading | 16px | 26px | 700 (Bold) |
| Body | 16px | 26px | 400 (Regular) |
| Icon | 16px | 26px | 900 (Solid) |
Notification Width
| Token | Value | Notes |
|---|---|---|
Notification/Width/modal | 736px | Max-width for snackbar (matches --card-xl) |
Notification/Width/embedded | TBD | Needs verification from Figma. Alerts are inline and fill container width. |
Shadow Blur Sizes (Responsive)
Blur radius values for shadow effects, defined in the Responsive collection. Values are consistent across breakpoints. Pending final verification from Figma.
| Token | Value (px) | Scope |
|---|---|---|
Shadow/XS | 2 | EFFECT_FLOAT |
Shadow/S | 4 | EFFECT_FLOAT |
Shadow/M | 8 | EFFECT_FLOAT |
Shadow/L | 16 | EFFECT_FLOAT |
Shadow/default | 4 | EFFECT_FLOAT |
Alert
Informs users of a process outcome or system event. Compact, inline feedback for form validation results, status changes, and contextual messages.
Overview
The Alert is a compact, inline notification surface used to communicate outcomes or events. For example, if a user submits a form, the alert provides feedback about an error that occurred or confirms the form was completed successfully. Four status variants (Default, Success, Error, Information) control the color of all elements within the alert. The component contains an optional status icon, an optional bold heading, body copy, an optional text link (reusing the Text Link S component), and an optional close button. All optional elements can be included or omitted independently.
Variants
Default
Neutral container with default border. Icon and heading use the headings color, body and close use the body color. Suitable for general feedback or neutral messages.
Success
Green background and border. All text, icons, and the close button use the success color. Use for positive confirmations such as saves, completions, or sends.
Error
Red background and border. All text, icons, and the close button use the error color. Use for failures or issues that need user attention.
Information
Teal background and border. All text, icons, and the close button use the information color. Use for informational notices or system-level messages.
Accessibility Considerations
WCAG 2.1 AA target. Alerts are inline, persistent elements and should not auto-dismiss. They must be perceivable by assistive technology.
Keyboard Interactions
| Key | Action |
|---|---|
Tab | Moves focus to the link or close button inside the alert |
Enter / Space | Activates the focused close button or link |
ARIA and Semantics
- Use
role="status"for non-critical updates (default, success, information) so screen readers announce content politely. - Use
role="alert"for error alerts to assert immediate attention. - Include
aria-live="polite"(or"assertive"for errors) so content is announced when it appears dynamically. - The close button must have
aria-label="Dismiss"since it contains only an icon. - If the alert appears after a user action (form submit, etc.), focus management should move the user near the alert.
Construction
The alert is a compact single-row layout. The text link reuses the Text Link S component with status type classes for color matching.
Scale and Spacing
| Property | Value | Token |
|---|---|---|
| Container padding | 12px | --padding-s-m |
| Container border radius | 4px | --radius-s |
| Container border width | 1px | Fixed |
| Main gap (icon-text to close) | 8px | --padding-s |
| Icon-to-text gap | 8px | --padding-s |
| Text internal gap (heading / body / link) | 2px | --padding-xxs |
| Icon container width | 14px | Fixed |
| Close button width | 16px | Fixed |
Styles
| Element | Font / Size |
|---|---|
| Heading | Lato Bold (700), 16px/26px, -0.08px tracking |
| Body | Lato Regular (400), 14px/20px |
| Link text | Lato Bold (700), 14px/20px (Text Link S) |
| Link chevron | FA6 Solid (900), 14px/20px (Text Link S icon) |
| Status icon | FA6 Solid (900), 16px/26px, 0.4px tracking |
| Close icon | FA6 Solid (900), 16px/26px, 0.4px tracking |
Color Tokens
| Element | Default | Success | Error | Information |
|---|---|---|---|---|
| Container bg | --surface-default-container | --surface-success | --surface-error | --surface-information |
| Container border | --border-default | --border-success | --border-error | --border-information |
| Icon | --text-headings | --text-status-success | --text-status-error | --text-status-information |
| Heading | --text-headings | --text-status-success | --text-status-error | --text-status-information |
| Body text | --text-body | --text-status-success | --text-status-error | --text-status-information |
| Link text + chevron | --text-body | --text-status-success | --text-status-error | --text-status-information |
| Close icon | --text-body | --text-status-success | --text-status-error | --text-status-information |
Application and Context
For Developers
| Attribute / Class | Type | Default | Description |
|---|---|---|---|
.alert | Container | Default status | Root element. 12px padded, 4px rounded, bordered. |
.alert-success | Modifier | — | Applies success colors to all elements (bg, border, text, icons). |
.alert-error | Modifier | — | Applies error colors to all elements. |
.alert-information | Modifier | — | Applies information colors to all elements. |
.alert-icon-text | Layout | — | Flex row for icon and text block. Fills available space. |
.alert-icon | Element | — | Optional. 14px-wide icon container. Omit to hide. |
.alert-text | Layout | — | Flex column for heading, body, and link. 2px gap. |
.alert-heading | Element | — | Optional. Bold title line. |
.alert-body | Element | — | Body copy in Body S (14px). |
.text-link.text-link-s | Sub-component | — | Optional. Reuses the Text Link S component. Add .type-success, .type-error, or .type-information to match the alert status. |
.alert-close | Element | — | Optional. Dismiss button. Requires aria-label="Dismiss". |
For Designers
Use alerts for inline, persistent feedback tied to a specific action or context, such as form validation or account status.
Don't use alerts for transient notifications. Use the Snackbar component for brief, non-blocking feedback instead.
Match the status variant to the message type so color communicates meaning at a glance.
Don't use the Error variant for informational messages. Reserve red for actual failures or issues requiring action.
Spacing Guidance
| Context | Property | Value | Token |
|---|---|---|---|
| Container internal | padding | 12px | --padding-s-m |
| Icon-text to close | gap | 8px | --padding-s |
| Icon to text | gap | 8px | --padding-s |
| Text block internal | gap | 2px | --padding-xxs |
| Link text to chevron | gap | 8px | --padding-s |
| Alert to surrounding content | margin | 16-24px | --padding-m / --padding-l |
Usage Examples
Default with All Options
<div class="alert" role="status" aria-live="polite">
<div class="alert-icon-text">
<div class="alert-icon">
<i class="fa-solid fa-circle-info"></i>
</div>
<div class="alert-text">
<div class="alert-heading">Changes saved</div>
<div class="alert-body">Your profile has been updated.</div>
<a class="text-link text-link-s" href="#">
<span class="link-label">Link</span>
<i class="link-icon fa-solid fa-chevron-right"></i>
</a>
</div>
</div>
<button class="alert-close" aria-label="Dismiss">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
Error Alert
<div class="alert alert-error" role="alert" aria-live="assertive">
<div class="alert-icon-text">
<div class="alert-icon">
<i class="fa-solid fa-circle-exclamation"></i>
</div>
<div class="alert-text">
<div class="alert-heading">Submission failed</div>
<div class="alert-body">Please correct the errors and try again.</div>
<a class="text-link text-link-s type-error" href="#">
<span class="link-label">Link</span>
<i class="link-icon fa-solid fa-chevron-right"></i>
</a>
</div>
</div>
<button class="alert-close" aria-label="Dismiss">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
Minimal (No Icon, No Link, No Close)
<div class="alert alert-success" role="status" aria-live="polite">
<div class="alert-icon-text">
<div class="alert-text">
<div class="alert-body">Registration complete.</div>
</div>
</div>
</div>
Breadcrumbs
A horizontal navigation pattern that shows the user's current location within a site hierarchy. Each crumb is a clickable link except the last item, which represents the current page.
Overview
Breadcrumbs help users understand where they are in a multi-level navigation structure and provide quick access to parent pages. The component uses Small Link (S) typography (Lato Bold, 14px/18px) with --text-body as the default color. On hover, all elements within a crumb (text, icons, separator) change to --text-action-hover. A chevron-right icon (fa-chevron-right) separates each crumb. The first crumb includes a home icon (fa-house). The last crumb (current page) displays a persistent underline and has no trailing separator.
Variants
Assembled Breadcrumb
Full breadcrumb trail with home icon, intermediate links, and current page.
First Item
Includes a home icon (left), label text, and a chevron-right separator (right).
Middle Item
Label text with a chevron-right separator. No home icon.
Last Item (Current Page)
Label text only, with a persistent underline. No trailing chevron separator.
Accessibility Considerations
WCAG 2.1 AA target. Breadcrumbs expose hierarchy and current location; focus order follows the visual trail left to right.
Keyboard Interactions
| Key | Action |
|---|---|
Tab / Shift+Tab | Moves focus between crumb links |
Enter | Activates the focused link (parent pages only) |
ARIA and Semantics
- Wrap the trail in
<nav aria-label="Breadcrumb">(or another concise label). - Add
aria-current="page"to the last (current) item. - Home icon and chevron separators use
aria-hidden="true". - Disabled crumbs use
<span>instead of<a>so they are not in the tab order. - Focus indicator: 2px solid
--border-action, 6px outline offset from the crumb, 10px radius (--radius-focus-ring), matching the Small Link focus treatment in Figma.
Construction
Typography
| Role | Font | Weight | Size / Line Height | Token |
|---|---|---|---|---|
| Crumb label | Lato | Bold (700) | 14px / 18px | Body/Text Links/S (--body-s-size, --body-s-line) |
| Icons (home, chevron) | FA6 Solid | 900 | 14px / 18px | Body/Icons/S icon |
Color Tokens
| Element | State | Token |
|---|---|---|
| Text + icons (non-current) | Default | --text-body |
| Text + icons (non-current) | Hover | --text-action-hover |
| Current page label | Default | --text-body + underline (.link-label) |
| Current page label | Hover | --text-action-hover + underline |
| Disabled background | Disabled | --border-default |
| Disabled text + icons | Disabled | --text-body |
Focus Ring
| Property | Value | Token |
|---|---|---|
| Border width | 2px | --border-width-m-action |
| Border color | --border-action | #F78958 |
| Border radius | 10px | --radius-focus-ring |
| Offset | 6px | Matches Figma Small Link / breadcrumb crumb focus inset |
Spacing
| Property | Value | Token |
|---|---|---|
| Gap between crumbs | 8px | --padding-s |
| Internal gap (icon, label, chevron) | 8px | --padding-s |
| Disabled horizontal padding | 2px | Fixed |
| Disabled border-radius | 4px | --radius-s |
Application and Context
For Developers
| Attribute / Class | Type | Description |
|---|---|---|
.breadcrumb | Container | Applied to <nav>. Flex row, 8px gap, centered items. |
.breadcrumb-item | Crumb | Applied to <a> (or <span> when disabled). Inline-flex, 8px internal gap, body color by default. |
.breadcrumb-icon | Element | Font Awesome icon inside a crumb (home, chevron). |
.current | Modifier | Last item (current page). Adds persistent underline to .link-label. |
.link-label | Element | Required on current item. Wrap the current page text for underline styling. |
.is-disabled | State | Disabled crumb on <span>; gray background, no pointer events. |
aria-current="page" | Attribute | On the current page crumb. |
aria-hidden="true" | Attribute | On decorative <i> icons. |
For Designers
Always start the breadcrumb trail with the home icon on the first item to provide a consistent anchor point.
Don't omit the home icon from the first breadcrumb item. Users rely on it as a visual indicator of the navigation root.
Mark the last item as the current page with a persistent underline to clearly indicate the user's location.
Don't make the current page item look identical to the other crumbs. The underline distinguishes it as non-navigable context.
Keep breadcrumb labels short and descriptive. Use page titles or category names, not full sentences.
Don't use breadcrumbs for flat navigation structures. They are intended for hierarchical, multi-level page structures only.
Usage Examples
Standard Breadcrumb
<nav class="breadcrumb" aria-label="Breadcrumb">
<a class="breadcrumb-item" href="/home">
<i class="breadcrumb-icon fa-solid fa-house" aria-hidden="true"></i>
<span>Home</span>
<i class="breadcrumb-icon fa-solid fa-chevron-right" aria-hidden="true"></i>
</a>
<a class="breadcrumb-item" href="/components">
<span>Components</span>
<i class="breadcrumb-icon fa-solid fa-chevron-right" aria-hidden="true"></i>
</a>
<a class="breadcrumb-item current" href="/components/button" aria-current="page">
<span class="link-label">Button</span>
</a>
</nav>
Breadcrumb with Disabled Item
<nav class="breadcrumb" aria-label="Breadcrumb">
<a class="breadcrumb-item" href="/home">
<i class="breadcrumb-icon fa-solid fa-house" aria-hidden="true"></i>
<span>Home</span>
<i class="breadcrumb-icon fa-solid fa-chevron-right" aria-hidden="true"></i>
</a>
<span class="breadcrumb-item is-disabled">
<span>Archived</span>
<i class="breadcrumb-icon fa-solid fa-chevron-right" aria-hidden="true"></i>
</span>
<a class="breadcrumb-item current" href="/archived/report" aria-current="page">
<span class="link-label">Report</span>
</a>
</nav>
Button
Triggers an action or event. The primary mechanism for user-initiated operations across dialogs, forms, cards, and toolbars.
Overview
Buttons communicate actions users can take. They use the primary (orange) action color to establish visual hierarchy. Two variants exist: Primary (filled) for the most important action, and Outline (bordered) for secondary actions. Four sizes (S, M, L, XL) allow density control across layouts. Each button supports optional leading and trailing icons via Font Awesome 6.
Variants
Primary (Filled)
Solid background with a lighter border. White text and icons on the default state. Used for the highest-priority action in a view.
Outline (Bordered)
Transparent background with a colored border. Text and icons use the action color. Used for secondary actions or when paired alongside a Primary button.
Sizes
Four density levels control vertical padding and font size.
Accessibility Considerations
WCAG 2.1 AA target. Uses the native <button> element with implicit role="button".
Keyboard Interactions
| Key | Action |
|---|---|
Enter | Activates the button |
Space | Activates the button |
Tab | Moves focus to next focusable element |
Shift+Tab | Moves focus to previous focusable element |
Focus Management
- Focus ring: 2px solid border, offset 6px outward from the button edge, matching the button's pill radius (48px).
- Primary focus ring uses
Border/Button/Primary/border(#f78958). - Outline focus ring uses
Border/Button/Primary/outline(#e96620). - Focus state does not change background or text color from default.
Disabled State
- Uses
aria-disabled="true"to remain in tab order for discoverability. - Text and icon color:
Text/Action/action-disabled(#979797). Reduced contrast but still legible against both filled (#f5f5f5) and page (#fafafa) backgrounds.
Construction
Scale and Spacing
| Property | S | M | L | XL | Token |
|---|---|---|---|---|---|
| Padding top/bottom | 8px | 8px | 12px | 16px | Fixed/padding/S, S, M-S, M |
| Padding left/right | 16px | 24px | 32px | 32px | Fixed/padding/M, L, XL, XL |
| Internal gap | 8px | 8px | 8px | 8px | Fixed/padding/S |
Styles
| Property | Value | Token |
|---|---|---|
| Border radius | 48px (pill) | Corner Radius/M button |
| Border width | 2px | Border/Width/M action |
| Font (S) | Lato Regular 14px/18px | Body/Body S |
| Font (M, L) | Lato Regular 16px/26px | Body/Body M |
| Font (XL) | Lato Regular 20px/32px | Body/Body L |
| Icon font (S) | FA6 Solid 14px/18px | Body/Icons/S icon |
| Icon font (M, L) | FA6 Solid 16px/26px | Body/Icons/M icon |
| Icon font (XL) | FA6 Solid 20px/32px | Body/Icons/L icon |
| Icon width | 18px | Fixed |
Color Tokens: Primary Variant
| State | Background | Border | Text | Icons |
|---|---|---|---|---|
| Default | Surface/Button/Primary/bg#e96620 | Border/Button/Primary/border#f78958 | Text/Button/Primary/text#ffffff | Icons/Button/Primary/icon#ffffff |
| Hover | Surface/Button/Primary/bg-hover#f78958 | Border/Button/Primary/action-hover#ffa984 | Text/Button/Primary/text-hover#fff1eb | Icons/Button/Primary/icon-hover#fff1eb |
| Focus | (same as default) | (same as default) | (same as default) | (same as default) |
| Pressed | Surface/Button/Primary/bg-pressed#d14000 | Border/Button/Primary/action-pressed#e96620 | Text/Button/Primary/text-pressed#ffc7ae | Icons/Button/Primary/icon-pressed#ffc7ae |
| Disabled | Surface/Button/Primary/bg-disabled#f5f5f5 | Border/Action/disabled#e9e9e9 | Text/Button/Primary/text-disabled#979797 | Icons/Button/Primary/icon-disabled#979797 |
Color Tokens: Outline Variant
| State | Background | Border | Text | Icons |
|---|---|---|---|---|
| Default | Surface/Global/page#fafafa | Border/Button/Primary/outline#e96620 | Text/Action/action#e96620 | Icons/Action/action#e96620 |
| Hover | (same as default) | Border/Button/Primary/outline border-hover#ffa984 | Text/Action/action-hover#f78958 | Icons/Action/action-hover#f78958 |
| Focus | (same as default) | (same as default) | (same as default) | (same as default) |
| Pressed | (same as default) | Border/Button/Primary/outline border-pressed#b11500 | Text/Button/Primary/outline text-pressed#d14000 | Icons/Button/Primary/outline icon-pressed#d14000 |
| Disabled | (same as default) | Border/Action/disabled#e9e9e9 | Text/Action/action-disabled#979797 | Icons/Action/action-disabled#979797 |
Application and Context
For Developers
| Attribute / Class | Type | Default | Description |
|---|---|---|---|
.btn-primary / .btn-outline | Variant class | .btn-primary | Visual variant: filled or bordered. |
.btn-s / .btn-m / .btn-l / .btn-xl | Size class | .btn-m | Density: S (8px/16px pad, Body S), M (8px/24px, Body M), L (12px/24px, Body M), XL (16px/24px, Body L). |
.btn-icon | Child element | — | Wrap icon characters (Font Awesome unicode) in a span with this class. Optional leading and/or trailing position. |
disabled / .is-disabled | State | — | Native disabled attribute or .is-disabled class for non-button elements. Use aria-disabled="true" when keeping in tab order. |
For Designers
Use Primary for the single most important action in a view. Pair with Outline for secondary actions.
Don't place multiple Primary buttons in the same context. Hierarchy is lost when everything is emphasized.
Use XL for hero sections only. Use L sparingly for prominent CTAs below a hero. Use M as the standard across the board. Use S for tight spaces or secondary buttons when absolutely necessary.
Don't mix button sizes within the same action group. Consistent density reinforces visual rhythm.
Spacing Guidance
| Context | Token | Value |
|---|---|---|
| Gap between button and adjacent button | Fixed/padding/S | 8px |
| Gap between icon and label (internal) | Fixed/padding/S | 8px |
| Button group to form field above | Jumper/S-XS | 32px (desktop) / 16px (mobile) |
Usage Examples
Basic
<button class="btn btn-primary btn-m" type="button">Submit</button>
<button class="btn btn-outline btn-m" type="button">Cancel</button>
With Icons
<button class="btn btn-primary btn-m" type="button">
<span class="btn-icon"></span>
Button
<span class="btn-icon"></span>
</button>
All Sizes
<button class="btn btn-primary btn-s" type="button">Small</button>
<button class="btn btn-primary btn-m" type="button">Medium</button>
<button class="btn btn-primary btn-l" type="button">Large</button>
<button class="btn btn-primary btn-xl" type="button">XLarge</button>
Disabled
<button class="btn btn-primary btn-m" disabled aria-disabled="true">
<span class="btn-icon"></span>
Unavailable
</button>
<button class="btn btn-outline btn-m" disabled aria-disabled="true">
<span class="btn-icon"></span>
Unavailable
</button>
Button Group
A segmented control that groups related actions into a single horizontal strip, allowing users to choose between options or toggle views.
Overview
Button groups combine two or more related actions into a connected row of segments. Each segment behaves like an individual button but shares a container border and divider treatment that communicates they belong together. Two types exist: Labeled (icon + text) and Icon-only (compact, icon without text). Items support four states: Default, Hover, Selected, and Disabled. The component uses role="group" with an aria-label describing the group purpose.
Variants
Labeled (Icon + Text)
Each segment contains a leading icon and a text label. The full-width variant used when labels are needed to clarify the action.
Icon-only
Compact variant where each segment contains only an icon. Use when space is tight and the icons are universally understood. Every icon-only item must have an aria-label.
Accessibility Considerations
WCAG 2.1 AA target. The group container communicates that the buttons are related.
Keyboard Interactions
| Key | Action |
|---|---|
Tab | Moves focus into/out of the group. Each item is a separate tab stop. |
Enter / Space | Activates the focused item (selects it). |
ARIA Requirements
- The container must have
role="group"and anaria-labeldescribing the group purpose. - The selected item should have
aria-pressed="true"(toggle behavior) or be managed viaaria-currentfor navigation. - Icon-only items must include
aria-labelon each button. - Disabled items use
aria-disabled="true"to remain discoverable.
Focus Management
- Focus indicator: 2px solid outline in
--surface-action, inset 2px. Contained within the group border. - Focus does not change background or text color from the current state.
Construction
Scale and Spacing
| Property | Labeled | Icon-only | Token |
|---|---|---|---|
| Padding (vertical) | 8px | 12px | Fixed/padding/S / M-S |
| Padding (horizontal) | 16px | 12px | Fixed/padding/M / M-S |
| Gap (icon to label) | 8px | n/a | Fixed/padding/S |
| Divider width | 2px | Border/Width/M action | |
| Container border | 1px | Border/Width/M default | |
| Container radius | 4px | Corner Radius/S | |
Styles
| Property | Value | Token |
|---|---|---|
| Font family | Lato Regular | font |
| Font size | 16px | Body M/font size |
| Line height | 24px | Body M/line height |
| Letter spacing | 0.16px (0.01em) | Body M |
| Icon font | FA6 Free Solid, 16px | Body M/M icons/font |
| Icon width | 16px | Fixed |
Color Tokens
| State | Background | Border / Divider | Text / Icon |
|---|---|---|---|
| Default | Surface/Global/page#fafafa |
Border/Global/default#e9e9e9 |
Text/Default/body#464646 |
| Hover | Surface/Action/action-hover#f78958 |
Border/Action/action-hover#ffa984 |
Text/Action/action-on-hover#fff1eb |
| Selected | Surface/Action/action#e96620 |
Border/Action/action#f78958 |
Text/Action/action-on#ffffff |
| Disabled | Surface/Action/disabled#f5f5f5 |
Border/Action/disabled#e9e9e9 |
Text/Action/action-disabled#979797 |
Application and Context
For Developers
| Attribute / Class | Type | Default | Description |
|---|---|---|---|
.btn-group | Container | — | Wraps all items. Provides outer border, radius, and overflow clipping. |
.btn-group-item | Child element | — | Each segment inside the group. Use <button> elements. |
.btn-group-icon-only | Modifier | — | Applied to .btn-group-item for icon-only segments. Sets uniform padding. |
.selected | State class | — | Marks the currently active/selected item. |
role="group" | ARIA role | — | Required on the container to communicate the grouping relationship. |
aria-label | Attribute | — | Required on the container. Also required on each icon-only item. |
aria-pressed | Attribute | "false" | Set to "true" on the selected item when used as a toggle group. |
For Designers
Use button groups for related actions that benefit from visual proximity, such as view toggles (list/grid), alignment options, or filter sets.
Don't use a button group for unrelated actions. If the actions serve different purposes, use individual buttons instead.
Use the labeled variant when the action needs explanation. Use icon-only when space is tight and icons are universally recognized.
Don't mix labeled and icon-only items within the same group. Keep all segments consistent.
Spacing Guidance
| Context | Spacing | Token |
|---|---|---|
| Between button group and adjacent content | 24px | --space-300 |
| Between stacked button groups | 16px | --space-200 |
| Button group within a toolbar | 16px gap | --space-200 |
Usage Examples
Labeled Group
<div class="btn-group" role="group" aria-label="View options">
<button class="btn-group-item" type="button">
<i class="btn-group-icon fa-solid fa-list"></i> List
</button>
<button class="btn-group-item selected" type="button"
aria-pressed="true">
<i class="btn-group-icon fa-solid fa-grip"></i> Grid
</button>
<button class="btn-group-item" type="button">
<i class="btn-group-icon fa-solid fa-table"></i> Table
</button>
</div>
Icon-only Group
<div class="btn-group" role="group" aria-label="Actions">
<button class="btn-group-item btn-group-icon-only"
type="button" aria-label="Mail">
<i class="btn-group-icon fa-solid fa-envelope"></i>
</button>
<button class="btn-group-item btn-group-icon-only"
type="button" aria-label="Info">
<i class="btn-group-icon fa-solid fa-circle-info"></i>
</button>
<button class="btn-group-item btn-group-icon-only"
type="button" aria-label="Favorite">
<i class="btn-group-icon fa-solid fa-heart"></i>
</button>
</div>
With Selected and Disabled Items
<div class="btn-group" role="group" aria-label="Alignment">
<button class="btn-group-item selected" type="button"
aria-pressed="true">
<i class="btn-group-icon fa-solid fa-align-left"></i> Left
</button>
<button class="btn-group-item" type="button">
<i class="btn-group-icon fa-solid fa-align-center"></i> Center
</button>
<button class="btn-group-item" type="button"
disabled aria-disabled="true">
<i class="btn-group-icon fa-solid fa-align-right"></i> Right
</button>
</div>
Carousel
The Carousel is used to scroll through a series of images, cards, or other content blocks. Left and right navigation buttons advance the content, and a progress bar reflects the current scroll position.
Overview
The Carousel provides horizontal navigation through a set of content items that exceed the visible viewport. It consists of three atomic parts: a left button (chevron-left), a progress bar, and a right button (chevron-right). The buttons advance the content one item at a time, and the progress bar fills proportionally to indicate how far the user has scrolled. The carousel is designed to be scalable: any number of content items can be placed in the track, and the controls adapt automatically. When the first item is visible, the left button is disabled. When the last item is visible, the right button is disabled.
Variants
Navigation Button - Left
Pill-shaped button with a chevron-left icon. Navigates the carousel content one step backward.
Navigation Button - Right
Pill-shaped button with a chevron-right icon. Navigates the carousel content one step forward.
Progress Bar
An 8px-tall track with a proportional fill that reflects the current scroll position. The fill width and position update as the user navigates.
Assembled Controls
The three atomic parts combined into a single control bar: left button, progress bar, and right button.
Working Demo
A functional carousel with content cards. Click the left/right buttons to scroll. The progress bar updates to reflect the current position. The left button is disabled at the start; the right button is disabled at the end.
Accessibility Considerations
WCAG 2.1 AA target. The carousel must be fully navigable by keyboard and clearly indicate its boundaries and current position to assistive technology.
Keyboard Interactions
| Key | Action |
|---|---|
Tab | Moves focus between the previous button, content items, and next button |
Enter / Space | Activates the focused navigation button |
Arrow Left | Scrolls to the previous item (when a nav button is focused) |
Arrow Right | Scrolls to the next item (when a nav button is focused) |
ARIA and Semantics
- The carousel container should use
role="region"with anaria-labeldescribing the content (e.g., "Featured stories"). - Navigation buttons require
aria-label="Previous"andaria-label="Next". - Disabled buttons must have the
disabledattribute andaria-disabled="true". - The progress bar is decorative and should use
aria-hidden="true". - Content items within the track should have
role="group"witharia-roledescription="slide"andaria-label="N of M".
Construction
The Carousel is composed of three atomic parts (left button, progress bar, right button) assembled into a control bar, placed below or above a viewport that clips the scrollable content track.
Scale and Spacing
| Property | Value | Token |
|---|---|---|
| Button width | 34px | Fixed |
| Button height | 32px | Fixed |
| Button padding (horizontal) | 12px | --padding-s-m |
| Button padding (vertical) | 4px | --padding-xs |
| Button border radius | 48px | --radius-xxl |
| Button border width | 2px | --border-width-m-action |
| Bar height | 8px | Fixed |
| Bar border radius | 48px | --radius-xxl |
| Controls gap | 16px | --padding-m |
| Track item gap | 16px | --padding-m |
| Focus ring width | 2px | --border-width-m-action |
| Focus ring offset | -5px (inset) | Fixed |
| Focus ring radius | 48px | --radius-xxl |
Styles
| Element | Font / Size |
|---|---|
| Chevron icon | FA6 Solid (900), 16px/26px, 0.4px tracking |
Color Tokens
| Element | Default | Hover | Focus | Pressed | Disabled |
|---|---|---|---|---|---|
| Button bg | --surface-default-container | --surface-action-hover | --surface-default-container | --surface-default-container | --surface-disabled |
| Button border | --border-action | --border-action-hover | --border-action | --border-action-pressed | --border-disabled |
| Button icon | --text-action | --text-action-hover | --text-action | --surface-action-pressed | --text-action-disabled |
| Bar track | --surface-indicator-bg (same as Progress Bar track) | ||||
| Bar fill | --surface-action | ||||
| Focus ring | --border-action | ||||
Application and Context
For Developers
| Attribute / Class | Type | Default | Description |
|---|---|---|---|
.carousel | Container | - | Root wrapper. Add data-carousel to enable JS behavior. |
.carousel-viewport | Layout | - | Overflow-hidden container that clips the content track. |
.carousel-track | Layout | - | Flex row holding all content items. Translates horizontally on navigation. |
.carousel-controls | Layout | - | Flex row containing the two buttons and the progress bar. |
.carousel-btn | Element | Enabled | Navigation button. 34x32px pill shape with a chevron icon. |
.carousel-btn.is-disabled | Modifier | - | Grays out the button. Add disabled attribute. |
.carousel-bar | Element | - | Progress track. Flex-grows to fill available space between buttons. |
.carousel-bar-fill | Element | - | Absolute-positioned fill inside the bar. JS sets left and width. |
data-carousel | Data attr | - | Place on .carousel to auto-initialize JS behavior. |
data-carousel-prev | Data attr | - | Place on the left .carousel-btn. |
data-carousel-next | Data attr | - | Place on the right .carousel-btn. |
data-carousel-fill | Data attr | - | Place on .carousel-bar-fill for JS to update position. |
For Designers
Use the Carousel for horizontally scrollable content sets such as image galleries, card collections, or featured stories.
Don't use the Carousel for sequential workflows or step-by-step processes. Use the Tab Bar component instead.
Ensure carousel content items are uniform in size so the progress bar accurately represents position.
Don't hide critical content inside a carousel. Important actions or information should be visible without scrolling.
Spacing Guidance
| Context | Property | Value | Token |
|---|---|---|---|
| Controls bar internal | gap | 16px | --padding-m |
| Track items | gap | 16px | --padding-m |
| Viewport to controls | gap | 16px | --padding-m |
| Carousel to surrounding content | margin | 16-24px | --padding-m / --padding-l |
Usage Examples
Basic Carousel
<div class="carousel" data-carousel role="region" aria-label="Featured stories">
<div class="carousel-viewport">
<div class="carousel-track">
<div role="group" aria-roledescription="slide" aria-label="1 of 4">...</div>
<div role="group" aria-roledescription="slide" aria-label="2 of 4">...</div>
<div role="group" aria-roledescription="slide" aria-label="3 of 4">...</div>
<div role="group" aria-roledescription="slide" aria-label="4 of 4">...</div>
</div>
</div>
<div class="carousel-controls">
<button class="carousel-btn" type="button" aria-label="Previous" data-carousel-prev>
<i class="fa-solid fa-chevron-left"></i>
</button>
<div class="carousel-bar" aria-hidden="true">
<div class="carousel-bar-fill" data-carousel-fill></div>
</div>
<button class="carousel-btn" type="button" aria-label="Next" data-carousel-next>
<i class="fa-solid fa-chevron-right"></i>
</button>
</div>
</div>
Controls Only (No Content)
<div class="carousel-controls">
<button class="carousel-btn" type="button" aria-label="Previous">
<i class="fa-solid fa-chevron-left"></i>
</button>
<div class="carousel-bar" aria-hidden="true">
<div class="carousel-bar-fill" style="left:0%; width:25%;"></div>
</div>
<button class="carousel-btn" type="button" aria-label="Next">
<i class="fa-solid fa-chevron-right"></i>
</button>
</div>
Checkbox
A form control that allows users to select one or more options from a set. Pairs a 24x24 indicator box with an optional text label.
Overview
The checkbox is a binary selection control. When checked, it displays a Font Awesome check icon inside an action-colored fill. When unchecked, it renders as a bordered empty square. The component supports five states (default, hover, focus, disabled, error) across both selected and unselected types. An optional label sits to the right with an 8px gap.
Variants
Unselected
Empty checkbox with action-colored border and page-colored background. Shows the available selection target.
Selected
Filled checkbox with a white check icon. The action-colored background confirms the selection.
With Label
Checkbox paired with a Body M label (Lato Regular, 16px/26px) at an 8px gap.
Accessibility Considerations
WCAG 2.1 AA target. Checkboxes must be operable by keyboard and clearly communicate their checked/unchecked state to assistive technology.
Keyboard Interactions
| Key | Action |
|---|---|
Space | Toggles the checkbox between checked and unchecked |
Tab | Moves focus to the next focusable element |
Shift+Tab | Moves focus to the previous focusable element |
ARIA and Semantics
- Use native
<input type="checkbox">inside a<label>for automatic association. This provides the checked/unchecked state to screen readers without extra ARIA. - When the label wraps the input, no
for/idpairing is needed. - For checkbox groups, wrap in a
<fieldset>with a<legend>describing the group. - Error state should be paired with
aria-describedbypointing to an error message element andaria-invalid="true"on the input. - Disabled checkboxes use the native
disabledattribute, which removes them from tab order. - Focus ring: 2px solid
--border-action, 3px offset, 4px border-radius.
Construction
Scale and Spacing
| Property | Value | Token |
|---|---|---|
| Checkbox size | 24 x 24px | --space-300 |
| Border width | 1px | --space-10 |
| Border radius | 4px | --radius-s |
| Icon size | 16px | --body-m-size |
| Checkbox-to-label gap | 8px | --space-100 |
| Focus ring offset | 3px | n/a |
| Focus ring width | 2px | n/a |
Styles
| Property | Value |
|---|---|
| Icon font | Font Awesome 6 Free (Solid, weight 900) |
| Icon glyph | \f00c (check) |
| Icon letter-spacing | 0.45px |
| Label font | Lato Regular (400), 16px/26px |
| Label color | --text-body (#464646) |
Color Tokens
| State | Background | Border | Icon |
|---|---|---|---|
| Default (unchecked) | --surface-default-container | --border-action | n/a |
| Default (checked) | --surface-action | --border-action | --text-action-on (white) |
| Hover | --surface-action-hover | --border-action-hover | --text-action-hover |
| Focus | (same as default) | (same as default) | (same as default) |
| Disabled | --surface-disabled | --border-disabled | --text-action-disabled |
| Error | --surface-error | --border-error | --text-status-error |
Application and Context
For Developers
| Attribute / Class | Type | Description |
|---|---|---|
.form-checkbox | Container | Wrapping <label>. Flex row with 8px gap. |
input[type="checkbox"] | Element | Native checkbox with appearance: none for custom styling. |
checked | Attribute | Native checked state. Fills background and shows icon via ::after. |
disabled | Attribute | Native disabled. Grays out and removes from tab order. |
.has-error | Modifier | Added to the <input>. Applies error surface/border colors. |
.is-disabled | Modifier | Visual-only disabled state for documentation previews. |
.show-hover | Modifier | Forces hover appearance for documentation previews. |
.show-focus | Modifier | Forces focus ring for documentation previews. |
For Designers
Use checkboxes when users can select zero, one, or multiple options from a list.
Don't use a checkbox for a binary on/off setting where a toggle switch communicates instant effect.
Always provide a visible label. If space prevents it, use aria-label on the input.
Don't use a checkbox as a single required acknowledgment without clear context (e.g., "I agree to..." needs the full statement).
Spacing Guidance
| Context | Property | Value | Token |
|---|---|---|---|
| Checkbox to label | gap | 8px | --space-100 |
| Between stacked checkboxes | gap / margin-bottom | 8-16px | --space-100 to --space-200 |
| Checkbox group to next field | margin-bottom | 24px | --space-300 |
Usage Examples
Single Checkbox with Label
<label class="form-checkbox">
<input type="checkbox">
I agree to the Terms of Service
</label>
Checkbox Group
<fieldset>
<legend>Select your interests</legend>
<label class="form-checkbox">
<input type="checkbox" name="interests" value="design">
Design
</label>
<label class="form-checkbox">
<input type="checkbox" name="interests" value="dev">
Development
</label>
<label class="form-checkbox">
<input type="checkbox" name="interests" value="research">
Research
</label>
</fieldset>
Error State with Message
<label class="form-checkbox">
<input type="checkbox" class="has-error"
aria-invalid="true"
aria-describedby="cb-err">
Accept privacy policy
</label>
<span id="cb-err" class="input-hint hint-error">
You must accept to continue.
</span>
Disabled Checkbox
<label class="form-checkbox">
<input type="checkbox" disabled>
This option is unavailable
</label>
Icon Button
A compact button containing only an icon, used for actions where the icon alone communicates intent.
Overview
Icon buttons are a density-optimized variant of the standard button. They remove the text label and use uniform padding on all sides so the button renders as a circle. Two variants exist: Primary (filled) and Outline (bordered). Three sizes (S, M, L) match the standard button size scale. Icons are rendered via Font Awesome 6 Free (Solid weight). Because there is no visible label, every Icon Button must have an aria-label.
Variants
Primary (Filled)
Solid orange background with a lighter border. White icon. Same color token set as the standard Primary button.
Outline (Bordered)
Page-color background with an orange border. Icon uses the action color. Same color token set as the standard Outline button.
Accessibility Considerations
WCAG 2.1 AA target. Because there is no visible text label, an accessible name is required.
Keyboard Interactions
| Key | Action |
|---|---|
Enter | Activates the button |
Space | Activates the button |
Tab | Moves focus to next focusable element |
ARIA Requirements
- Every icon button must include
aria-labeldescribing the action (e.g.,aria-label="More information"). - If the icon button toggles state, use
aria-pressedto communicate the current value. - Focus ring: same 2px solid, 6px outward offset, pill radius as the standard button.
Construction
Scale and Spacing
| Property | S | M | L | Token |
|---|---|---|---|---|
| Padding (all sides) | 8px | 12px | 16px | Fixed/padding/S, M-S, M |
Styles
| State | Color | Token |
|---|---|---|
| Border radius | 48px (pill, renders as circle) | Corner Radius/M button |
| Border width | 2px | Border/Width/M action |
| Icon font (S, M) | FA6 Solid 16px/26px | Body/Icons/M icon |
| Icon font (L) | FA6 Solid 20px/32px | Body/Icons/L icon |
| Icon width | 16px | Fixed |
Color Tokens
Identical to the standard Button component. See the Button color token tables for the full Primary and Outline state mappings.
Application and Context
For Developers
| Attribute / Class | Type | Default | Description |
|---|---|---|---|
.btn-icon-only | Modifier class | — | Added alongside .btn and a variant/size class. Overrides padding to be uniform on all sides and sets icon line-height to 1, producing a circle. |
aria-label | Attribute | — | Required. Provides accessible name since there is no visible text. |
For Designers
Use icon buttons for well-understood actions (close, search, menu toggle) where the icon alone is unambiguous.
Don't use icon buttons for actions that require explanation. If users might not recognize the icon, use a standard button with a text label.
Usage Examples
Basic
<button class="btn btn-primary btn-icon-only btn-m"
type="button"
aria-label="More information">
<i class="btn-icon fa-solid fa-circle-info"></i>
</button>
<button class="btn btn-outline btn-icon-only btn-m"
type="button"
aria-label="More information">
<i class="btn-icon fa-solid fa-circle-info"></i>
</button>
All Sizes
<button class="btn btn-primary btn-icon-only btn-s" aria-label="Info">
<i class="btn-icon fa-solid fa-circle-info"></i>
</button>
<button class="btn btn-primary btn-icon-only btn-m" aria-label="Info">
<i class="btn-icon fa-solid fa-circle-info"></i>
</button>
<button class="btn btn-primary btn-icon-only btn-l" aria-label="Info">
<i class="btn-icon fa-solid fa-circle-info"></i>
</button>
Disabled
<button class="btn btn-primary btn-icon-only btn-m"
disabled aria-disabled="true" aria-label="Info">
<i class="btn-icon fa-solid fa-circle-info"></i>
</button>
Input
A single-line text field that combines a label, input field with optional icons, and hint text into a complete form control.
Overview
The Input component is a composed form control made up of three sub-elements: a Label (with optional info icon and required indicator), a Field (with optional leading and trailing icons), and optional Hint Text. It supports four interaction states (default, hover, focus, disabled) and three validation states (error, success, information). The Input is also used within the Forms pattern. All field text uses Lato Regular at Body M (16px/26px). Hint text uses Body S (14px/18px). Icons use Font Awesome 6 Solid at 16px.
Variants
Label
The label sits above the field. It pairs text with an optional trailing info icon. The required variant positions a red asterisk to the left of the label text (per WCAG best practice).
Field States
The field supports four interaction states combined with two content states: Pre-filled (placeholder text visible, field is empty) and Not Pre-filled (user has entered text). In the pre-filled state, icons and text use --text-placeholder to signal the field awaits input.
Pre-filled (Placeholder)
When the field is empty, placeholder text and icons display in --text-placeholder.
Not Pre-filled (Typed Text)
Once the user has entered text, icons and text use --text-body or the current interaction-state color.
Validation States
Validation feedback colors the field border, trailing icon, and hint text to indicate status. The trailing icon changes to fa-circle-check for success and fa-circle-xmark for error and information.
Assembled Input
The full Input component with Label, Field, and Hint Text.
Accessibility Considerations
WCAG 2.1 AA target. Every input must be associated with a visible label for assistive technology.
Keyboard Interactions
| Key | Action |
|---|---|
Tab | Moves focus into the input field. Disabled fields are skipped automatically. |
| Any character | Enters text at the cursor position (standard native input behavior). |
Backspace / Delete | Removes characters before / after cursor. |
ARIA and Semantics
- Use
<label for="id">or wrap the input in a<label>to associate the label with the field. - Add
aria-required="true"to required fields. The visual asterisk is decorative via CSS::before. - Connect hint/validation text via
aria-describedbypointing to the hint element'sid. - Set
aria-invalid="true"on the input when in an error state. - Leading and trailing icons are decorative and must include
aria-hidden="true". - Use the native
disabledattribute on<input>to remove disabled fields from tab order automatically.
Construction
The Input is composed of three sub-elements stacked vertically: Label, Field, and Hint Text. All three sit inside a flex-column container (.input-group) with 8px gap.
Scale and Spacing
| Property | Value | Token |
|---|---|---|
| Field padding (vertical) | 8px | --padding-s |
| Field padding (horizontal) | 12px | --padding-s-m |
| Field icon-to-text gap | 8px | --padding-s |
| Field min-width | 336px | Fixed |
| Field border width | 1px | Border/Width/M default |
| Field border radius | 8px | --radius-m |
| Focus ring width | 2px | Border/Width/M action |
| Focus ring offset | -5px (inset) | Fixed |
| Focus ring radius | 12px | Fixed |
| Label-to-field gap | 8px | --padding-s |
| Field-to-hint gap | 4px | --padding-xs |
| Label icon gap | 8px | --padding-s |
Styles
| Element | Font / Size |
|---|---|
| Label text | Lato Regular (400), 16px/26px, 0.16px tracking |
| Label icon | FA6 Solid (900), 16px/26px, 0.4px tracking |
| Field text (typed) | Lato Regular (400), 16px/26px, 0.16px tracking |
| Field placeholder | Lato Regular (400), 16px/26px, 0.16px tracking |
| Field icons | FA6 Solid (900), 16px/26px, 0.4px tracking |
| Hint text | Lato Regular (400), 14px/18px |
Color Tokens
| Element | Default | Hover | Focus | Disabled |
|---|---|---|---|---|
| Field border | --border-accent | --border-action-hover | --border-action | --border-disabled |
| Field background | --surface-default-container | --surface-default-container | --surface-default-container | --surface-disabled |
| Text / icons (typed) | --text-body | --text-action-hover | --text-body | --text-action-disabled |
| Text / icons (placeholder) | --text-placeholder | --text-action-hover | --text-placeholder | --text-action-disabled |
| Placeholder text | --text-placeholder | --text-placeholder | ||
| Focus ring | — | --border-action (2px) | — | |
| Label text | --text-body | |||
| Hint text | --text-body | |||
| Element | Error | Error Hover | Success | Success Hover | Information | Info Hover |
|---|---|---|---|---|---|---|
| Field border | --border-error | --border-error | --border-success | --border-success | --border-information | --border-information |
| Field bg | --surface-default-container | --surface-error | --surface-default-container | --surface-success | --surface-default-container | --surface-information |
| Hint text | --text-status-error | --text-status-error | --text-status-success | --text-status-success | --text-status-information | --text-status-information |
| Status icon | --text-status-error | --text-status-error | --text-status-success | --text-status-success | --text-status-information | --text-status-information |
Application and Context
For Developers
| Attribute / Class | Type | Default | Description |
|---|---|---|---|
.input-group | Container | — | Flex column, 8px gap. Wraps label, field, and hint. |
.input-label | Label | — | Applied to <label>. Flex row, 8px icon gap, body color. |
.label-required | Modifier | — | Adds red asterisk via ::before. Requires position: relative on the label. |
.label-icon | Element | — | Info icon inside the label. FA Solid, inherits color. |
.form-field | Field container | Default state | Flex row, 8px gap, 8px/12px padding, 1px border, 8px radius. |
.is-placeholder | State | — | Applies placeholder color to icons when the field is empty (pre-filled). Remove when the user has typed text. |
.field-icon | Element | — | Leading or trailing icon inside the field. FA Solid, 16px. |
.field-status-icon | Modifier | — | Applied to trailing icon for validation. Colors the icon per status. |
.is-disabled | State | — | Disabled field. Also set the native disabled attribute on the input element. |
.field-error | Validation | — | Error border + hover bg. Pair with .hint-error and .field-status-icon. |
.field-success | Validation | — | Success border + hover bg. Pair with .hint-success and .field-status-icon. |
.field-information | Validation | — | Information border + hover bg. Pair with .hint-information and .field-status-icon. |
.input-hint | Element | — | Hint or validation message below the field. Body S, body color by default. |
.hint-error | Modifier | — | Colors hint with --text-status-error. |
.hint-success | Modifier | — | Colors hint with --text-status-success. |
.hint-information | Modifier | — | Colors hint with --text-status-information. |
For Designers
Always pair an input field with a visible label. Placeholder text alone is not a substitute for a label.
Don't rely on placeholder text as the primary label. It disappears once the user starts typing and is not reliably read by screen readers.
Use hint text to provide context or formatting guidance (e.g., "Enter your first name"). Keep it brief and actionable.
Don't use hint text for critical instructions. If the information is essential, place it in the label or nearby body text instead.
Show validation messages immediately after the user leaves the field or submits the form. Use the appropriate status color.
Don't rely solely on color to communicate validation status. The border, icon, and text message work together to convey the state.
Spacing Guidance
| Context | Property | Value | Token |
|---|---|---|---|
| Input group internal | gap | 8px / 4px | --padding-s (label-to-field) / --padding-xs (field-to-hint) |
| Field internal | padding | 8px 12px | --padding-s / --padding-s-m |
| Field icon to text | gap | 8px | --padding-s |
| Label icon to text | gap | 8px | --padding-s |
| Between stacked inputs | gap | 24px | --space-300 |
| Last input to submit | gap | 24px | --space-300 |
Usage Examples
Basic Input with Label and Hint
<div class="input-group">
<label class="input-label" for="fname">
First name
<i class="label-icon fa-solid fa-circle-question" aria-hidden="true"></i>
</label>
<div class="form-field">
<i class="field-icon fa-solid fa-user" aria-hidden="true"></i>
<input type="text" id="fname" placeholder="First name">
<i class="field-icon fa-solid fa-circle-xmark" aria-hidden="true"></i>
</div>
<span class="input-hint">Hint text</span>
</div>
Required Input
<div class="input-group">
<label class="input-label label-required" for="email">
Email
<i class="label-icon fa-solid fa-circle-question" aria-hidden="true"></i>
</label>
<div class="form-field">
<i class="field-icon fa-solid fa-envelope" aria-hidden="true"></i>
<input type="email" id="email" placeholder="Email address"
aria-required="true">
<i class="field-icon fa-solid fa-circle-xmark" aria-hidden="true"></i>
</div>
</div>
Input with Error Validation
<div class="input-group">
<label class="input-label" for="email-err">Email</label>
<div class="form-field field-error">
<i class="field-icon fa-solid fa-envelope" aria-hidden="true"></i>
<input type="email" id="email-err" placeholder="Email"
aria-invalid="true" aria-describedby="email-err-hint">
<i class="field-icon field-status-icon
fa-solid fa-circle-xmark" aria-hidden="true"></i>
</div>
<span class="input-hint hint-error"
id="email-err-hint">Please enter a valid email.</span>
</div>
Input with Success Validation
<div class="input-group">
<label class="input-label" for="email-ok">Email</label>
<div class="form-field field-success">
<i class="field-icon fa-solid fa-envelope" aria-hidden="true"></i>
<input type="email" id="email-ok" placeholder="Email"
aria-describedby="email-ok-hint">
<i class="field-icon field-status-icon
fa-solid fa-circle-check" aria-hidden="true"></i>
</div>
<span class="input-hint hint-success"
id="email-ok-hint">Email verified successfully.</span>
</div>
Selector Accordion
Overview
A collapsible dropdown list that organises selectable options under a category header. Clicking the header expands a panel of options; clicking an option selects it.
The Selector Accordion is built from two sub-components: a Category header (with label and chevron icon) and Option items. The category header toggles between collapsed and expanded states. When expanded, options appear in a connected list below the header. The last option receives bottom border-radius to close the visual container. Options can be unselected, selected, or disabled. The category header and all options share a consistent set of interaction states (default, hover, pressed, disabled). Category text uses Body M (16px/26px). Option text uses Body S (14px/18px). The chevron icon uses Font Awesome 6 Solid.
Variants
Category Header (Collapsed)
The category header in its collapsed state. A chevron-down icon indicates the selector accordion can be expanded.
Category Header (Expanded)
When expanded, the category header loses its bottom border-radius, connecting visually to the options below. The chevron rotates 180 degrees.
Option States (Drop-down options that are unselected)
Individual option rows within an expanded selector accordion. Options use Body S typography. These show the unselected variant.
Option States (Drop-down options that are selected)
Options in the selected state, indicating the currently chosen value.
Last Option Focus
The last option in a selector accordion receives bottom border-radius on its focus ring using --radius-focus-ring (10px) to match the container shape.
Assembled Selector Accordion
The full Selector Accordion component with a label, an info icon, and a dropdown. The category header toggles expanded/collapsed on click.
Working Demo
Click the category header to expand or collapse. Click an option to select it.
Accessibility Considerations
WCAG 2.1 AA target. The Selector Accordion should behave as a disclosure widget: the category header is a <button> that toggles visibility of the options panel.
Keyboard Interactions
| Key | Action |
|---|---|
Tab | Moves focus to the category header. When expanded, Tab moves through option items. |
Enter / Space | On the category header: toggles expanded/collapsed. On an option: selects it. |
Escape | Collapses the selector accordion and returns focus to the category header. |
ARIA and Semantics
- The category header must be a
<button>witharia-expanded="true|false". - Use
aria-controlson the header pointing to the options container'sid. - Options should use
role="option"or be native<button>elements. - The selected option should include
aria-selected="true"oraria-current="true". - Disabled options must include the
disabledattribute andaria-disabled="true". - The chevron icon is decorative: add
aria-hidden="true".
Construction
The Selector Accordion is composed of a label row, a category header, and an options panel. The label sits above the header in a flex-column container with an 8px gap. Wrap the category button and the options panel in .sel-accordion-stack so the pair gets a definite width inside flex layouts (for example documentation state rows or the Filter pattern). The options panel stacks vertically below the header with no gap, creating a seamless bordered list.
Scale and Spacing
| Property | Value | Token |
|---|---|---|
| Category padding (vertical) | 8px | --padding-s |
| Category padding (horizontal) | 16px | --padding-m |
| Category label-to-icon spacing | space-between, min gap 16px | CSS justify-content + gap: var(--jumper-XXS-XXXS) |
| Expand/collapse animation | 300ms ease-in-out | Height transition with ease-in-out easing |
| Category border width | 1px | Border/Width/M default |
| Category border radius | 8px | --radius-m |
| Option padding (vertical) | 8px | --padding-s |
| Option padding (horizontal) | 16px | --padding-m |
| Option border width | 1px (left, right, bottom) | Border/Width/M default |
| Last option border radius (bottom) | 8px | --radius-m |
| Label-to-header gap | 8px | --jumper-XXXS-Tiny |
| Min width | 128px (desktop) | --card-xs |
| Focus ring width | 2px | Border/Width/M action |
| Focus ring offset (category) | -5px (inset) | Fixed |
| Focus ring radius (category, collapsed) | 10px | --radius-focus-ring |
| Focus ring radius (category, expanded) | 10px 10px 0 0 | --radius-focus-ring (top corners only) |
| Focus ring offset (option) | top: -4px, left/right/bottom: -5px | Fixed |
| Focus ring radius (option) | 0px (rectangular) | Fixed |
| Focus ring radius (last option) | 0 0 10px 10px | --radius-focus-ring (bottom corners only) |
Styles
| Element | Font / Size |
|---|---|
| Label text | Lato Regular (400), 16px/26px, 0.16px tracking |
| Label icon | FA6 Solid (900), 16px/26px, 0.4px tracking |
| Category text | Lato Regular (400), 16px/26px, 0.16px tracking |
| Category chevron | FA6 Solid (900), 16px/26px, 0.4px tracking |
| Option text | Lato Regular (400), 14px/18px |
Color Tokens for Category Header
| Element | Default | Hover | Pressed | Disabled |
|---|---|---|---|---|
| Category bg | --surface-default-container |
--surface-action-hover |
--surface-action-pressed |
--surface-disabled |
| Category border | --border-default |
--border-action-hover |
--border-action-pressed |
--border-disabled |
| Category text / icon | --text-body |
--text-action-on-hover |
--text-action-on-pressed |
--text-action-disabled |
| Focus ring | --border-action (2px) |
|||
Color Tokens for Drop-Down Options
| Element | Default | Hover | Pressed | Selected | Selected + Hover | Selected + Pressed | Disabled |
|---|---|---|---|---|---|---|---|
| Option bg | --surface-default-container |
--surface-action-hover |
--surface-action-pressed |
--surface-action |
--surface-action-hover |
--surface-action-pressed |
--surface-disabled |
| Option border | --border-default |
--border-action-hover |
--border-action-pressed |
--border-action |
--border-action-hover |
--border-action-pressed |
--border-disabled |
| Option text | --text-body |
--text-action-on-hover |
--text-action-on-pressed |
--text-action-on |
--text-action-on-hover |
--text-action-on-pressed |
--text-action-disabled |
| Focus ring | --border-action (2px) |
||||||
Application and Context
For Developers
| Attribute / Class | Type | Default | Description |
|---|---|---|---|
.sel-accordion-group | Container | — | Flex column wrapper. Holds the label and the category + options block. |
.sel-accordion-label | Element | — | Label row above the category header. Supports an optional .label-icon. |
.sel-accordion-category | Element | Collapsed | The expandable header button. Must be a <button> with aria-expanded. |
.sel-accordion-icon | Element | — | Chevron icon inside the category header. Rotates 180deg when expanded. |
.is-expanded | State | — | Applied to the category header when the options panel is visible. Removes bottom border-radius and rotates the chevron. |
.sel-accordion-dropdown | Container | — | Flex column wrapper for option items. Hidden when collapsed. |
.sel-accordion-option | Element | — | An individual option row. The last child automatically gets bottom border-radius. |
.is-selected | State | — | Marks the currently selected option. Applies action background and white text. |
.is-disabled | State | — | Disabled state. Also set the native disabled attribute and aria-disabled="true". |
For Designers
Always pair the selector accordion with a visible label so users understand what category they are selecting from.
Don't nest selector accordions inside selector accordions. For multi-level hierarchies, consider a different navigation pattern.
Keep option labels short and scannable. Users should quickly identify the right choice.
Don't use the Selector Accordion as a navigation element. It is designed for selecting values, not routing between pages.
Spacing Guidance
| Context | Recommendation | Token |
|---|---|---|
| Label above category | 8px gap | --jumper-XXXS-Tiny |
| Internal category padding | 8px vertical, 16px horizontal | --padding-s / --padding-m |
| Internal option padding | 8px vertical, 16px horizontal | --padding-s / --padding-m |
| Selector Accordion in a form layout | 16px below preceding field | --padding-m |
Usage Examples
Basic Selector Accordion (collapsed)
<div class="sel-accordion-group">
<div class="sel-accordion-label">Category <i class="label-icon fa-solid fa-circle-question"></i></div>
<div>
<button class="sel-accordion-category" type="button" aria-expanded="false" aria-controls="sel-accordion-options">
<span>Select option</span>
<i class="sel-accordion-icon fa-solid fa-chevron-down" aria-hidden="true"></i>
</button>
<div class="sel-accordion-dropdown" id="sel-accordion-options" hidden>
<button class="sel-accordion-option" type="button">Option A</button>
<button class="sel-accordion-option" type="button">Option B</button>
<button class="sel-accordion-option" type="button">Option C</button>
</div>
</div>
</div>
Expanded with selected option
<div class="sel-accordion-group">
<div class="sel-accordion-label">Category</div>
<div>
<button class="sel-accordion-category is-expanded" type="button" aria-expanded="true" aria-controls="sel-accordion-opts">
<span>Category</span>
<i class="sel-accordion-icon fa-solid fa-chevron-down" aria-hidden="true"></i>
</button>
<div class="sel-accordion-dropdown" id="sel-accordion-opts">
<button class="sel-accordion-option" type="button">Option A</button>
<button class="sel-accordion-option is-selected" type="button" aria-selected="true">Option B</button>
<button class="sel-accordion-option" type="button">Option C</button>
</div>
</div>
</div>
Progress Bar
A horizontal indicator that communicates the completion status of a task or process.
Overview
The progress bar shows how far along a process is by filling a horizontal track from left to right. It comes in two sizes: Desktop (8px track) and Mobile (4px track). An optional label row above the bar displays a text descriptor and a percentage value. The bar uses the action color for the fill against a light tinted track. Width of the fill is controlled via inline style or JavaScript.
Variants
Desktop (Default)
8px track height with a 20px/24px bold label. Use on desktop viewports.
Mobile (Small)
4px track height with a 16px/20px bold label. Use on mobile viewports or in compact layouts.
Accessibility Considerations
WCAG 2.1 AA target. Progress bars must communicate their current value to assistive technology.
Keyboard Interactions
| Key | Action |
|---|---|
| n/a | The progress bar is not interactive and does not receive focus. |
ARIA and Semantics
- Use
role="progressbar"on the track element. - Set
aria-valuenowto the current percentage (e.g.80). - Set
aria-valuemin="0"andaria-valuemax="100". - Include
aria-labeldescribing the process (e.g."Upload progress") when no visible label is present. - When a visible label exists, use
aria-labelledbypointing to the label element'sid. - For indeterminate progress (unknown completion), omit
aria-valuenowand add an animation to the fill.
Construction
Scale and Spacing
| Property | Desktop | Mobile | Token |
|---|---|---|---|
| Track height | 8px | 4px | Fixed |
| Track border radius | 48px | --radius-xxl | |
| Label-to-track gap | 16px | --jumper-XXS-XXXS | |
| Label text-to-value gap | 8px | --jumper-XXXS-Tiny | |
Styles
| Property | Desktop | Mobile |
|---|---|---|
| Label font | Lato Bold (700), 20px/24px, -0.1px tracking | Lato Bold (700), 16px/20px, -0.08px tracking |
| Label color | --text-headings (#393939) | |
| Track background | --surface-action-hover (#FFF1EB) | |
| Fill color | --surface-action (#E96620) | |
| Fill border radius | 48px (--radius-xxl) | |
Color Tokens
| Element | Token | Value |
|---|---|---|
| Label text | --text-headings | #393939 |
| Track background | --surface-action-hover | #FFF1EB |
| Fill | --surface-action | #E96620 |
Application and Context
For Developers
| Attribute / Class | Type | Default | Description |
|---|---|---|---|
.progress-bar | Container | Desktop size | Root flex-column wrapper. Full width of parent. |
.progress-bar-sm | Modifier | — | Switches to mobile size (4px track, 16px/20px label). |
.progress-bar-label | Element | — | Optional. Flex row containing text and value. |
.progress-bar-label-text | Element | — | Descriptor text (e.g. "Completion:"). Non-wrapping. |
.progress-bar-label-value | Element | — | Percentage or status value (e.g. "80%"). |
.progress-bar-track | Element | — | Background track. Carries role="progressbar". |
.progress-bar-fill | Element | — | Filled portion. Set width via inline style or JS. |
For Designers
Use progress bars for operations with a known completion percentage (uploads, multi-step forms, loading sequences).
Don't use a progress bar for indeterminate waits where completion is unknown. Use a spinner or skeleton instead.
Pair the bar with a visible label so users understand what process is being tracked.
Don't use multiple progress bars stacked without clear labels distinguishing each process.
Spacing Guidance
| Context | Property | Value | Token |
|---|---|---|---|
| Label to track | gap | 16px | --jumper-XXS-XXXS |
| Label text to value | gap | 8px | --jumper-XXXS-Tiny |
| Progress bar to adjacent content | margin | 16-24px | --jumper-XXS-XXXS / --jumper-XS-XXS |
Usage Examples
Desktop with Label
<div class="progress-bar">
<div class="progress-bar-label">
<span class="progress-bar-label-text">Completion:</span>
<span class="progress-bar-label-value">80%</span>
</div>
<div class="progress-bar-track"
role="progressbar"
aria-valuenow="80"
aria-valuemin="0"
aria-valuemax="100"
aria-label="Completion">
<div class="progress-bar-fill" style="width:80%"></div>
</div>
</div>
Mobile without Label
<div class="progress-bar progress-bar-sm">
<div class="progress-bar-track"
role="progressbar"
aria-valuenow="60"
aria-valuemin="0"
aria-valuemax="100"
aria-label="Upload progress">
<div class="progress-bar-fill" style="width:60%"></div>
</div>
</div>
Desktop without Label
<div class="progress-bar">
<div class="progress-bar-track"
role="progressbar"
aria-valuenow="45"
aria-valuemin="0"
aria-valuemax="100"
aria-label="Processing">
<div class="progress-bar-fill" style="width:45%"></div>
</div>
</div>
Radio Button
A form control that allows users to select exactly one option from a mutually exclusive set. Pairs a 24px circular indicator with an optional text label.
Overview
The radio button is a single-select control used inside groups of two or more options. When selected, it displays a small white dot centered inside an action-colored fill. When unselected, it renders as a bordered empty circle. The component supports five states (default, hover, focus, disabled, error) across both selected and unselected types. An optional label sits to the right with an 8px gap. The container has 4px vertical padding to provide comfortable touch/click targets.
Variants
Unselected
Empty circle with action-colored border. Shows the available selection target.
Selected
Filled circle with a centered white dot. The action-colored background confirms the selection.
With Label
Radio button paired with a Body M label (Lato Regular, 16px/26px) at an 8px gap.
Accessibility Considerations
WCAG 2.1 AA target. Radio buttons must be operable by keyboard and clearly communicate their selected/unselected state to assistive technology.
Keyboard Interactions
| Key | Action |
|---|---|
Tab | Moves focus into the radio group (to the selected item, or the first item if none selected) |
Arrow Down / Right | Selects the next radio button in the group |
Arrow Up / Left | Selects the previous radio button in the group |
Space | Selects the focused radio button (if not already selected) |
ARIA and Semantics
- Use native
<input type="radio">inside a<label>for automatic association. All radios in a group must share the samenameattribute. - Wrap the group in a
<fieldset>with a<legend>describing the question or category. - For custom implementations, use
role="radiogroup"on the container androle="radio"witharia-checkedon each option. - Error state should be paired with
aria-describedbypointing to an error message element andaria-invalid="true"on the input. - Disabled radio buttons use the native
disabledattribute, which removes them from tab order. - Focus ring: 2px solid
--border-action, 2px offset, fully rounded.
Construction
Scale and Spacing
| Property | Value | Token |
|---|---|---|
| Radio button size | 24 x 24px | --space-300 |
| Inner dot size | 8 x 8px | --space-100 |
| Border width | 1px | --space-10 |
| Border radius | 104px (fully round) | --radius-xxl / 104px |
| Radio-to-label gap | 8px | --space-100 |
| Container vertical padding | 4px | --space-50 |
| Focus ring offset | 2px | n/a |
| Focus ring width | 2px | n/a |
Styles
| Property | Value |
|---|---|
| Inner dot | 8px circle, centered via absolute positioning |
| Label font | Lato Regular (400), 16px/26px |
| Label color | --text-body (#464646) |
Color Tokens
| State | Background | Border | Dot |
|---|---|---|---|
| Default (unselected) | --surface-default-container | --border-action | n/a |
| Default (selected) | --surface-action | --border-action | white |
| Hover | --surface-action-hover | --border-action-hover | --text-action-hover |
| Focus | (same as default) | (same as default) | (same as default) |
| Disabled | --surface-disabled | --border-disabled | white |
| Error | --surface-error | --border-error | --text-status-error |
Application and Context
For Developers
| Attribute / Class | Type | Description |
|---|---|---|
.form-radio | Container | Wrapping <label>. Flex row with 8px gap and 4px vertical padding. |
input[type="radio"] | Element | Native radio with appearance: none for custom styling. All radios in a group share the same name. |
checked | Attribute | Native checked state. Fills background and shows inner dot via ::after. |
disabled | Attribute | Native disabled. Grays out and removes from tab order. |
.has-error | Modifier | Added to the <input>. Applies error surface/border/dot colors. |
.is-disabled | Modifier | Visual-only disabled state for documentation previews. |
.show-hover | Modifier | Forces hover appearance for documentation previews. |
.show-focus | Modifier | Forces focus ring for documentation previews. |
For Designers
Use radio buttons when users must select exactly one option from a mutually exclusive set of two or more choices.
Don't use radio buttons for multi-select scenarios. Use checkboxes instead.
Always group radios inside a <fieldset> with a descriptive <legend> so the question context is clear.
Don't pre-select a radio option unless there is a sensible default. An empty initial state forces a deliberate choice.
Spacing Guidance
| Context | Property | Value | Token |
|---|---|---|---|
| Radio to label | gap | 8px | --space-100 |
| Between stacked radios | gap / margin-bottom | 8-16px | --space-100 to --space-200 |
| Radio group to next field | margin-bottom | 24px | --space-300 |
| Container vertical padding | padding-top / padding-bottom | 4px | --space-50 |
Usage Examples
Radio Group with Fieldset
<fieldset>
<legend>Preferred contact method</legend>
<label class="form-radio">
<input type="radio" name="contact" value="email">
Email
</label>
<label class="form-radio">
<input type="radio" name="contact" value="phone">
Phone
</label>
<label class="form-radio">
<input type="radio" name="contact" value="text">
Text message
</label>
</fieldset>
Pre-selected Option
<fieldset>
<legend>Frequency</legend>
<label class="form-radio">
<input type="radio" name="freq" value="daily" checked>
Daily
</label>
<label class="form-radio">
<input type="radio" name="freq" value="weekly">
Weekly
</label>
</fieldset>
Error State with Message
<fieldset aria-describedby="rb-err">
<legend>Select a plan</legend>
<label class="form-radio">
<input type="radio" name="plan" value="basic"
class="has-error" aria-invalid="true">
Basic
</label>
<label class="form-radio">
<input type="radio" name="plan" value="pro"
class="has-error" aria-invalid="true">
Pro
</label>
</fieldset>
<span id="rb-err" class="input-hint hint-error">
Please select a plan to continue.
</span>
Disabled Radio
<label class="form-radio">
<input type="radio" name="tier" value="enterprise" disabled>
Enterprise (coming soon)
</label>
Snackbar
The Snackbar as a brief, modal notification to the user for things such as: A new fundraising campaign just launched, a new press release has been posted to the website, fundraising and sign-ups have opened for enrollment for Team World Vision, the user started setting up their account but hasn’t finished, etc.
Overview
The snackbar is a lightweight feedback surface at a minimum of 336px width. It contains an optional status icon, a bold heading, body copy, an optional "Learn More" text link, a close button, and an optional progress bar (using the Progress Bar SM component) along the bottom edge. Four status variants (Default, Success, Error, Information) control the icon and heading color while body text stays neutral. All optional elements (icon, link, close, progress bar) can be included or omitted independently.
Variants
Default
Action/brand color for icon and heading. Use for general confirmations or neutral status updates.
Success
Green icon and heading for positive confirmations such as saves, completions, or sends.
Error
Red icon and heading for failures or destructive outcomes that need user attention.
Information
Teal icon and heading for informational notices or system-level messages.
Accessibility Considerations
WCAG 2.1 AA target. Snackbars must not block interaction and should be perceivable by assistive technology without stealing focus.
Keyboard Interactions
| Key | Action |
|---|---|
Tab | Moves focus to the link or close button inside the snackbar |
Enter / Space | Activates the focused close button or link |
Escape | Dismisses the snackbar when it has focus |
ARIA and Semantics
- Use
role="status"for non-critical updates (default, success, information) so screen readers announce content politely. - Use
role="alert"for error snackbars to assert immediate attention. - Include
aria-live="polite"(or"assertive"for errors) so content is announced when it appears. - The close button must have
aria-label="Dismiss"since it contains no visible text. - If the snackbar auto-dismisses, the timeout should be at least 5 seconds. Provide a way to pause or extend.
- The snackbar must not trap focus or prevent interaction with the rest of the page.
Construction
The snackbar composes several sub-elements. The embedded progress bar reuses the Progress Bar SM component. The text link reuses the Body Link M pattern from the Text Link component.
Scale and Spacing
| Property | Value | Token |
|---|---|---|
| Container width | 100% / max var(--card-xl) | --card-xl |
| Container border radius | 8px | --radius-m |
| Container border width | 1px | Fixed |
| Content padding | 16px | --padding-m |
| Content-to-close gap | 16px | --padding-m |
| Icon-to-text gap | 8px | --padding-s |
| Heading / body / link gap | 8px | --jumper-XXXS-Tiny |
| Icon container width | 20px | Fixed |
| Close button container width | 28px | Fixed |
| Progress bar height | 4px | Progress Bar SM |
Styles
| Element | Font / Size |
|---|---|
| Heading | Lato Bold (700), 20px/24px, -0.1px tracking |
| Body | Lato Regular (400), 16px/26px, 0.16px tracking |
| Link text | Lato Bold (700), 16px/26px, 0.16px tracking |
| Link chevron | FA6 Solid (900), 16px/26px, 0.4px tracking |
| Status icon | FA6 Solid (900), 20px/24px, 0.5px tracking |
| Close icon | FA6 Solid (900), 20px/32px, 0.05px tracking |
Color Tokens
| Element | Default | Success | Error | Information |
|---|---|---|---|---|
| Container bg | --surface-default-container | |||
| Container border | --border-default | |||
| Icon | --text-action | --text-status-success | --text-status-error | --text-status-information |
| Heading | --text-action | --text-status-success | --text-status-error | --text-status-information |
| Body text | --text-body | |||
| Link text + chevron | --text-action | --text-status-success | --text-status-error | --text-status-information |
| Close icon | --text-body | |||
| Progress track | --surface-action-hover | --surface-success | --surface-error | --surface-information |
| Progress fill | --surface-action | --text-status-success | --text-status-error | --text-status-information |
Application and Context
For Developers
| Attribute / Class | Type | Default | Description |
|---|---|---|---|
.snackbar | Container | Default status | Root element. 100% wide, max-width --card-xl (736px), rounded, bordered. |
.snackbar-success | Modifier | — | Applies success color to icon and heading. |
.snackbar-error | Modifier | — | Applies error color to icon and heading. |
.snackbar-information | Modifier | — | Applies information color to icon and heading. |
.snackbar-content | Layout | — | Flex row wrapping icon/text and close button. 16px padding. |
.snackbar-icon-text | Layout | — | Flex row for icon and text block. |
.snackbar-icon | Element | — | Optional. 20px-wide icon container. Omit to hide. |
.snackbar-text | Layout | — | Flex column for heading, body, and link. |
.snackbar-heading | Element | — | Bold title. Color set by status modifier. |
.snackbar-body | Element | — | Body copy in neutral color. |
.text-link.text-link-m | Sub-component | — | Optional. Reuses the Body Link M component. Hover and focus states inherit automatically. |
.snackbar-close | Element | — | Dismiss button. Requires aria-label="Dismiss". |
.progress-bar.progress-bar-sm | Sub-component | — | Optional. Embeds the Progress Bar SM at the bottom. |
For Designers
Use snackbars for brief, non-blocking feedback that confirms an action or surfaces a transient status update (campaign launched, enrollment opened, etc.).
Don't use snackbars for critical decisions that require user input. Use a dialog or modal instead.
Match the status variant to the message type so color communicates meaning at a glance.
Don't stack multiple snackbars at once. Queue them so only one is visible at a time.
Spacing Guidance
| Context | Property | Value | Token |
|---|---|---|---|
| Container internal | padding | 16px | --padding-m |
| Icon to text | gap | 8px | --padding-s |
| Content to close | gap | 16px | --padding-m |
| Text block internal | gap | 8px | --jumper-XXXS-Tiny |
| Link text to chevron | gap | 8px | --padding-s |
| Snackbar to viewport edge | offset | 16-24px | --padding-m / --padding-l |
Usage Examples
Default with All Options
<div class="snackbar" role="status" aria-live="polite">
<div class="snackbar-content">
<div class="snackbar-icon-text">
<div class="snackbar-icon">
<i class="fa-solid fa-circle-info"></i>
</div>
<div class="snackbar-text">
<div class="snackbar-heading">Changes saved</div>
<div class="snackbar-body">Your profile has been updated.</div>
<a class="text-link text-link-m" href="#">
<span class="link-label">Learn More</span>
<i class="link-icon fa-solid fa-chevron-right"></i>
</a>
</div>
</div>
<button class="snackbar-close" aria-label="Dismiss">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
<div class="progress-bar progress-bar-sm">
<div class="progress-bar-track">
<div class="progress-bar-fill" style="width:60%"></div>
</div>
</div>
</div>
Error without Progress Bar
<div class="snackbar snackbar-error" role="alert" aria-live="assertive">
<div class="snackbar-content">
<div class="snackbar-icon-text">
<div class="snackbar-icon">
<i class="fa-solid fa-circle-exclamation"></i>
</div>
<div class="snackbar-text">
<div class="snackbar-heading">Upload failed</div>
<div class="snackbar-body">File exceeds the 10 MB limit.</div>
</div>
</div>
<button class="snackbar-close" aria-label="Dismiss">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
</div>
Minimal (No Icon, No Link, No Progress)
<div class="snackbar" role="status" aria-live="polite">
<div class="snackbar-content">
<div class="snackbar-icon-text">
<div class="snackbar-text">
<div class="snackbar-heading">Item deleted</div>
<div class="snackbar-body">The record has been removed.</div>
</div>
</div>
<button class="snackbar-close" aria-label="Dismiss">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
</div>
Tab Bar
A horizontal navigation element that shows users where they are in a multi-step process. Users can see completed steps, the current step, and remaining steps, and click any previous step to navigate back.
Overview
The Tab Bar is a linear, horizontal stepper that communicates progress through a multi-step workflow. Each tab represents one step. The selected (current) tab is visually distinguished with a bold label and an orange bottom border. Unselected tabs use regular-weight text with no bottom border. Two layout variants exist: Icons (each tab has an optional leading icon and a text label) and Numbers (each tab has a numeric prefix and a text label, no icon). Four interaction states apply per tab: Default, Hover, Focus, and Disabled.
Variants
Tab Item - Unselected
Regular-weight label, no bottom border highlight. Used for steps the user has not yet reached or for completed steps they can revisit.
Tab Item - Selected
Bold label with a 2px orange bottom border. Indicates the current active step in the process.
Assembled - Icons Variant
Each tab contains an icon and a text label. The full bar spans horizontally with a 1px bottom border. The second tab is shown as selected.
Assembled - Numbers Variant
Each tab uses a numeric prefix instead of an icon (e.g., "1. Name", "2. Address"). The second tab is shown as selected.
Accessibility Considerations
WCAG 2.1 AA target. The Tab Bar uses a tablist role pattern so assistive technology can convey the number of steps, the current step, and allow keyboard navigation between them.
Keyboard Interactions
| Key | Action |
|---|---|
Tab | Moves focus into the tab bar, landing on the currently selected tab |
Arrow Left / Arrow Right | Moves focus between tabs within the bar |
Enter / Space | Activates the focused tab and navigates to that step |
Home | Moves focus to the first tab |
End | Moves focus to the last tab |
ARIA and Semantics
- The container should use
role="tablist"to identify the group of tabs. - Each tab should use
role="tab". - The selected tab should have
aria-selected="true"; all othersaria-selected="false". - Disabled tabs should have
aria-disabled="true"andtabindex="-1"to remove them from the tab order. - Use
aria-labelon the tablist container to describe the process (e.g., "Registration steps"). - Icons are decorative and must have
aria-hidden="true".
Construction
The Tab Bar is a single-row flex container with individual tab items. Each tab item is a flex row combining an optional icon and a label. The selected state is indicated by bold text and a 2px bottom border.
Scale and Spacing
| Property | Value | Token |
|---|---|---|
| Tab item padding | 8px | --padding-s |
| Icon-to-label gap | 8px | --padding-s |
| Bar bottom border | 1px | Fixed |
| Selected bottom border | 2px | Fixed |
| Focus ring width | 2px | Fixed |
| Focus ring offset (unselected) | -4px all sides | Fixed |
| Focus ring offset (selected) | -4px top/right/left, -6px bottom | Fixed |
| Focus ring radius | 4px | --radius-s |
Styles
| Element | Font / Size |
|---|---|
| Label (unselected) | Lato Regular (400), 16px/26px, 0.16px tracking |
| Label (selected) | Lato Bold (700), 16px/26px, 0.16px tracking |
| Icon | FA6 Solid (900), 16px/26px, 0.4px tracking |
Color Tokens
| Element | Default | Hover | Focus | Disabled |
|---|---|---|---|---|
| Tab background | transparent | --surface-action-hover | transparent | --surface-disabled |
| Label text | --text-body | --text-action-hover | --text-body | --text-action-disabled |
| Icon | --text-action | --text-action-hover | --text-action | --text-action-disabled |
| Selected bottom border | --border-action | --border-action | --border-action | --border-disabled |
| Bar bottom border | --border-default | |||
| Focus ring | --border-action | |||
Application and Context
For Developers
| Attribute / Class | Type | Default | Description |
|---|---|---|---|
.tab-bar | Container | - | Root flex container with a 1px bottom border. |
.tab-item | Element | Unselected | Individual tab. 8px padding, flex row with gap. Use <button> for interactivity. |
.tab-item.selected | Modifier | - | Marks the current step. Applies bold weight and orange bottom border. |
.tab-item.is-disabled | Modifier | - | Disables interaction. Gray background, muted text/icon. Add disabled attribute. |
.tab-icon | Element | - | Optional. Leading icon inside a tab item. Use FA6 icon class. |
.tab-label | Element | - | Text label. For the Numbers variant, include the number in the text (e.g., "1. Name"). |
role="tablist" | ARIA | - | Apply to the .tab-bar container. |
role="tab" | ARIA | - | Apply to each .tab-item. |
aria-selected | ARIA | false | Set to "true" on the selected tab. |
For Designers
Use the Tab Bar for multi-step workflows where users benefit from knowing their position and can navigate back to previous steps.
Don't use the Tab Bar for primary site navigation. It is designed for sequential, in-page processes.
Use the Icons variant when each step has a clear, recognizable icon that aids comprehension at a glance.
Don't mix the Icons and Numbers variants in the same tab bar. Pick one layout and apply it consistently.
Disable future steps when a user must complete the current step before proceeding.
Don't disable all tabs. At minimum, the current step must remain interactive.
Spacing Guidance
| Context | Property | Value | Token |
|---|---|---|---|
| Tab item internal | padding | 8px | --padding-s |
| Icon to label | gap | 8px | --padding-s |
| Tab bar to surrounding content | margin | 16-24px | --padding-m / --padding-l |
| Tab bar below content panel | gap | 16-24px | --padding-m / --padding-l |
Usage Examples
Icons Variant
<div class="tab-bar" role="tablist" aria-label="Registration steps">
<button class="tab-item" role="tab" aria-selected="false" type="button">
<span class="tab-icon fa-solid fa-user" aria-hidden="true"></span>
<span class="tab-label">Profile</span>
</button>
<button class="tab-item selected" role="tab" aria-selected="true" type="button">
<span class="tab-icon fa-solid fa-location-dot" aria-hidden="true"></span>
<span class="tab-label">Address</span>
</button>
<button class="tab-item" role="tab" aria-selected="false" type="button">
<span class="tab-icon fa-solid fa-phone" aria-hidden="true"></span>
<span class="tab-label">Contact</span>
</button>
<button class="tab-item is-disabled" role="tab" aria-selected="false" aria-disabled="true" tabindex="-1" type="button" disabled>
<span class="tab-icon fa-solid fa-credit-card" aria-hidden="true"></span>
<span class="tab-label">Billing</span>
</button>
</div>
Numbers Variant
<div class="tab-bar" role="tablist" aria-label="Checkout steps">
<button class="tab-item" role="tab" aria-selected="false" type="button">
<span class="tab-label">1. Name</span>
</button>
<button class="tab-item selected" role="tab" aria-selected="true" type="button">
<span class="tab-label">2. Address</span>
</button>
<button class="tab-item" role="tab" aria-selected="false" type="button">
<span class="tab-label">3. Contact</span>
</button>
<button class="tab-item" role="tab" aria-selected="false" type="button">
<span class="tab-label">4. Billing</span>
</button>
<button class="tab-item" role="tab" aria-selected="false" type="button">
<span class="tab-label">5. Review</span>
</button>
</div>
Text Link
An inline interactive element that navigates users to another page or section. Two sizes: Body Link (M) at 16px and Small Link (S) at 14px. Each supports optional left and right icons.
Overview
Text links sit within running text or stand alone as navigation aids. Both sizes support optional left and right Font Awesome icons. The Body Link (M) uses the action/orange color and underlines on hover. The Small Link (S) includes status type variants (Information, Success, Error) that use semantic color tokens. The Small Link (S) Default type uses --text-body color and is reserved exclusively for the Breadcrumbs and Navigation components. Do not use the body-colored Small Link Default in general content or standalone contexts.
Variants
Body Link (M)
Lato Bold, 16px/26px, action-colored. Label underlines on hover. Used for in-content links.
Small Link (S) - Default
Lato Bold, 14px/20px, body-colored (--text-body). Label underlines on hover. Restricted usage: this variant is reserved for the Breadcrumbs and Navigation components only. Do not use it in general content or standalone contexts.
Small Link (S) - Information
Uses the --text-status-information color for contextual informational links.
Small Link (S) - Success
Uses the --text-status-success color for positive-outcome links.
Small Link (S) - Error
Uses the --text-status-error color for destructive or error-context links.
Accessibility Considerations
WCAG 2.1 AA target. Links must be distinguishable from surrounding body text by more than color alone.
Keyboard Interactions
| Key | Action |
|---|---|
Enter | Follows the link |
Tab | Moves focus to next focusable element |
Shift+Tab | Moves focus to previous focusable element |
ARIA and Semantics
- Use native
<a href>elements for navigation links. Use<button>styled as a text link for in-page actions. - The bold weight (700) on M and S sizes provides a secondary visual cue beyond color, aiding color-blind users.
- Body Link (M): 2px
--border-actionfocus ring, 10px radius (--radius-focus-ring), inset 4px top/bottom and 8px/9px left/right from content (Figma). - Small Link (S): 2px
--border-actionoutline, 6px offset, same radius (matches Figma Small Link focus). - Disabled links should use a
<span>instead of<a>to remove from the tab order, or usearia-disabled="true"withtabindex="-1".
Construction
Typography
| Size | Font | Weight | Size / Line Height | Letter Spacing | Token |
|---|---|---|---|---|---|
| M (Body Link) | Lato | Bold (700) | 16px / 24px | 1% | Body/Text Links/M |
| M icons | FA6 Solid | 900 | 16px / 24px | 2.5% | Body/Icons/M icon |
| S (Small Link) | Lato | Bold (700) | 14px / 20px | 0% | Body/Text Links/S |
| S icons | FA6 Solid | 900 | 14px / 20px | 0% | Body/Icons/S icon |
Color Tokens
| Variant | Default | Hover | Token Prefix |
|---|---|---|---|
| Body Link (M) | --text-action | --text-action-hover + underline | Text/Action |
| Small Link S - Default | --text-body | --text-action-hover + underline | Text/Body / Text/Action |
| Small Link S - Information | --text-status-information | --text-status-information-hover + underline | Text/Status/information |
| Small Link S - Success | --text-status-success | --text-status-success-hover + underline | Text/Status/success |
| Small Link S - Error | --text-status-error | --text-status-error-hover + underline | Text/Status/error |
| Disabled (all) | --text-body on --border-default bg | n/a | 2px horizontal padding, 4px radius |
Focus Ring
| Size | Implementation | Offset / inset | Token |
|---|---|---|---|
| Body Link (M) | 2px border via ::after (no outline) | 4px top/bottom, 8px left, 9px right from content box (Figma) | --border-width-m-action, --border-action, --radius-focus-ring |
| Small Link (S) | 2px outline | 6px outline-offset (Figma uniform inset) | --border-width-m-action, --border-action, --radius-focus-ring |
Application and Context
For Developers
| Attribute / Class | Type | Description |
|---|---|---|
.text-link | Base class | Applied to <a> or <button>. Uses display: inline-flex with 8px gap. |
.text-link-m | Size class | Body Link. Action color, 16px/26px, bold. Underlines label on hover. |
.text-link-s | Size class | Small Link. Body color, 14px/20px, bold. The default (body-colored) variant is reserved for Breadcrumbs and Navigation only. |
.link-label | Element | Required. Wrap the text in <span class="link-label"> so hover underline only applies to the text, not icons. |
.link-icon | Element | Font Awesome icon inside the link. Never receives underline. |
.type-information / .type-success / .type-error | Modifier | S size only. Applies semantic status color. |
.is-disabled | State | Use on a <span> (not <a>) to remove from tab order. |
For Designers
Match the text link size to the surrounding body text: Body Link M with Body M text, Small Link S with Body S text.
Don't rely on color alone to indicate a link. The bold weight and hover underline provide secondary cues.
Reserve the body-colored Small Link (S) Default for the Breadcrumbs and Navigation components only.
Don't use the body-colored Small Link Default in general content, forms, or standalone contexts. Use a status type variant or the Body Link (M) instead.
Usage Examples
Body Link with Icons
<a class="text-link text-link-m" href="/guidelines">
<i class="link-icon fa-solid fa-circle-info"></i>
<span class="link-label">Design guidelines</span>
<i class="link-icon fa-solid fa-arrow-up-right-from-square"></i>
</a>
Small Link with Status Type
<a class="text-link text-link-s type-information" href="/status">
<i class="link-icon fa-solid fa-circle-info"></i>
<span class="link-label">View details</span>
<i class="link-icon fa-solid fa-arrow-up-right-from-square"></i>
</a>
Disabled Link
<span class="text-link text-link-m is-disabled" aria-disabled="true">
<i class="link-icon fa-solid fa-circle-info"></i>
<span class="link-label">Unavailable link</span>
<i class="link-icon fa-solid fa-arrow-up-right-from-square"></i>
</span>
Toggle
A binary switch that lets users turn a setting on or off with immediate effect. Pairs a 48x24 track with a sliding 26x16 knob and an optional text label.
Overview
The toggle (also called a switch) is a binary control for settings that take effect immediately. Unlike a checkbox, which typically requires a form submission, a toggle communicates instant state change. When "on," the knob slides to the right and the track fills with the action color. When "off," the knob rests on the left with a neutral track. The component supports four states (default, hover, focus, disabled) across both on and off positions. An optional label sits to the right with an 8px gap.
Variants
Off (Unselected)
Neutral track with action-colored border. Knob rests at the left edge.
On (Selected)
Action-colored track with the knob slid to the right. Confirms the setting is active.
With Label
Toggle paired with a Body M label (Lato Regular, 16px/26px) at an 8px gap.
Accessibility Considerations
WCAG 2.1 AA target. The toggle must communicate its on/off state to assistive technology and be fully operable by keyboard.
Keyboard Interactions
| Key | Action |
|---|---|
Space | Toggles the switch between on and off |
Tab | Moves focus to the next focusable element |
Shift+Tab | Moves focus to the previous focusable element |
ARIA and Semantics
- Uses a visually hidden
<input type="checkbox">under the hood, which provides native checked/unchecked semantics to screen readers. - Add
role="switch"to the input for AT that distinguishes switches from checkboxes. The checked state maps toaria-checked. - The wrapping
<label>automatically associates the text with the control. - Disabled toggles use the native
disabledattribute, which removes them from tab order. - Focus ring: 2px solid
--border-action, -6px inset, 104px border-radius to match the track shape.
Construction
Scale and Spacing
| Property | Value | Token |
|---|---|---|
| Track width | 48px | Fixed |
| Track height | 24px | Fixed |
| Knob size | 26 x 16px | Fixed |
| Track border width | 2px | Fixed |
| Border radius (track) | 104px (fully round) | Fixed |
| Border radius (knob) | 100px (pill) | Fixed |
| Knob OFF position | left: 2px, top: 2px | Fixed |
| Knob ON position | left: 16px, top: 2px | Fixed |
| Toggle-to-label gap | 8px | --padding-s |
| Focus ring offset | -6px (inset) | Fixed |
| Focus ring width | 2px | Fixed |
| Focus ring radius | 104px | Fixed |
Styles
| Property | Value |
|---|---|
| Knob shape | Pill (border-radius: 100px), absolutely positioned |
| Transition | 0.2s ease on background, border-color, transform |
| Label font | Lato Regular (400), 16px/26px |
| Label color | --text-body (#464646) |
Color Tokens
| State | Track Background | Track Border | Knob |
|---|---|---|---|
| Off (default) | --surface-action-on | 2px --border-action | --btn-icon-action |
| On (default) | --surface-action | 2px --border-action | --btn-icon-action-on |
| Hover (both) | --surface-action-hover | 2px --border-action-hover | --btn-icon-on-hover |
| Focus | (same as default) | (same as default) | (same as default) |
| Disabled | #f5f5f5 | 2px --border-default | --btn-icon-action-disabled |
Application and Context
For Developers
| Attribute / Class | Type | Description |
|---|---|---|
.form-toggle | Container | Wrapping <label>. Flex row with 8px gap. |
input[type="checkbox"] | Element | Visually hidden native checkbox. Drives checked state via CSS sibling selectors. |
.toggle-track | Element | The 48x24 pill-shaped track with 2px border. Styled via adjacent sibling of the hidden input. |
.toggle-knob | Element | The 26x16 pill-shaped knob, absolutely positioned inside the track. Slides from left: 2px (off) to left: 16px (on). |
checked | Attribute | Native checked. Fills track with action color and slides knob to the right. |
disabled | Attribute | Native disabled. Grays out track and knob. |
role="switch" | Attribute | Recommended. Tells screen readers this is a switch, not a checkbox. |
.show-hover | Modifier (track) | Forces hover appearance on the track for documentation previews. |
.show-focus | Modifier (track) | Forces focus ring on the track for documentation previews. |
.is-disabled | Modifier (track) | Visual-only disabled state for documentation previews. |
.show-checked | Modifier (track) | Forces checked appearance on the track for documentation previews. |
For Designers
Use a toggle for binary settings that take effect immediately (e.g., "Enable notifications," "Dark mode").
Don't use a toggle when the change requires a form submission to take effect. Use a checkbox instead.
Always provide a visible label. Position it to the right of the toggle for left-to-right reading flow.
Don't place toggles in a long vertical list for multi-select scenarios. Use checkboxes for that pattern.
Spacing Guidance
| Context | Property | Value | Token |
|---|---|---|---|
| Toggle to label | gap | 8px | --space-100 |
| Between stacked toggles | gap / margin-bottom | 16-24px | --space-200 to --space-300 |
| Toggle group to next section | margin-bottom | 24px | --space-300 |
Usage Examples
Basic Toggle with Label
<label class="form-toggle">
<input type="checkbox" role="switch">
<span class="toggle-track">
<span class="toggle-knob"></span>
</span>
Enable notifications
</label>
Pre-checked Toggle
<label class="form-toggle">
<input type="checkbox" role="switch" checked>
<span class="toggle-track">
<span class="toggle-knob"></span>
</span>
Dark mode
</label>
Disabled Toggle
<label class="form-toggle">
<input type="checkbox" role="switch" disabled>
<span class="toggle-track">
<span class="toggle-knob"></span>
</span>
Feature unavailable
</label>
Settings Group
<fieldset>
<legend>Notification preferences</legend>
<label class="form-toggle">
<input type="checkbox" role="switch" checked>
<span class="toggle-track">
<span class="toggle-knob"></span>
</span>
Email notifications
</label>
<label class="form-toggle">
<input type="checkbox" role="switch">
<span class="toggle-track">
<span class="toggle-knob"></span>
</span>
Push notifications
</label>
<label class="form-toggle">
<input type="checkbox" role="switch" checked>
<span class="toggle-track">
<span class="toggle-knob"></span>
</span>
SMS alerts
</label>
</fieldset>
Toggle Selector
Overview
A pill-shaped binary selector that lets users switch between two mutually exclusive options. An animated indicator pill slides between sides to reinforce which option is active. This component is intended for use as a sub-component within larger patterns (e.g., search filters, view toggles).
The Toggle Selector pairs two text labels inside a rounded container. Clicking an inactive label slides the internal pill to that side. Unlike the Toggle (switch), which represents on/off state, this component presents two named choices.
Variants
Mobile (40px height)
Default viewport. Uses --body-s typography (14px / 20px).
Desktop (46px height)
Uses --body-m typography (16px / 24px, 0.16px letter-spacing).
Text States
Text labels change color based on their active/inactive status and the current interaction state.
| State | Active Text (on pill) | Inactive Text (off pill) |
|---|---|---|
| Default | --text-action-on (white) | --text-action (#e96620) |
| Hover | --text-action-on-hover (#fff1eb) | --text-action-hover (#f78958) |
| Pressed | --text-action-on-pressed (#ffc7ae) | --text-action-pressed (#d14000) |
Text State Previews (Mobile)
Working Demo
Click either label to slide the selector. The animation uses a custom Bezier curve (cubic-bezier(0.4, 0, 0.58, 1)) at 300ms for a natural feel.
Mobile
Desktop
Accessibility
| Attribute | Detail |
|---|---|
| WCAG Target | AA (4.5:1 for text, 3:1 for non-text elements) |
| Role | Container uses role="radiogroup"; each option uses role="radio" |
| ARIA | Active option: aria-checked="true"; inactive: aria-checked="false" |
| Keyboard | Tab focuses the group; Arrow Left/Right switches between options; Enter/Space activates the focused option |
| Focus ring | ::after pseudo-element with inset: -5px, 2px solid --border-action, border-radius: 48px |
| Screen reader | Announce option label and selected state. Container aria-label describes the group purpose. |
Construction
Scale and Spacing
| Property | Mobile | Desktop |
|---|---|---|
| Container height | 40px | 46px |
| Container border | 2px solid --surface-action | |
| Container radius | 48px | |
| Pill inset (top/bottom/sides) | 1px | |
| Pill border | 1px solid --border-action | |
| Pill radius | 48px | |
| Option padding (horizontal) | 12px | 16px |
| Font size | 14px (--body-s-size) | 16px (--body-m-size) |
| Line height | 20px (--body-s-line) | 24px (--body-m-line) |
| Letter spacing | 0 | 0.16px |
| Font weight | 700 (Bold) | |
| Focus ring offset | -5px inset, 2px solid --border-action | |
Styles
| Element | Detail |
|---|---|
| Container shape | Pill (border-radius: 48px), overflow: clip |
| Pill shape | Pill (border-radius: 48px), absolutely positioned, covers 50% of the container width |
| Pill animation | cubic-bezier(0.4, 0, 0.58, 1) at 300ms on left and right properties |
| Text animation | color transitions at 300ms with the same Bezier curve |
| Layout | Inline flex, two equal-width button children |
Color Tokens
| Element | Token | Value (Light) |
|---|---|---|
| Container background | --surface-action-on | #ffffff |
| Container border | --surface-action | #e96620 |
| Pill background | --surface-action | #e96620 |
| Pill border | --border-action | #f78958 |
| Active text (default) | --text-action-on | #ffffff |
| Active text (hover) | --text-action-on-hover | #fff1eb |
| Active text (pressed) | --text-action-on-pressed | #ffc7ae |
| Inactive text (default) | --text-action | #e96620 |
| Inactive text (hover) | --text-action-hover | #f78958 |
| Inactive text (pressed) | --text-action-pressed | #d14000 |
| Focus ring | --border-action | #f78958 |
Application & Context
For Developers
| Class / Attribute | Role | Description |
|---|---|---|
.toggle-selector | Container | Outer pill container. Add .right-active to slide the pill right. |
.toggle-selector-desktop | Modifier | Applies desktop sizing (46px height, body-m typography). |
.right-active | State | Slides the pill to the right side. Toggled via JavaScript on click. |
.toggle-selector-pill | Indicator | The sliding pill element. Must be the first child of the container. |
.toggle-selector-option | Button | Each clickable text option. Use <button type="button">. |
.is-active | State (option) | Marks the currently selected option. Updates text color to on-action tokens. |
.show-hover | Preview modifier | Forces hover text color for documentation previews. |
.show-pressed | Preview modifier | Forces pressed text color for documentation previews. |
.show-focus | Preview modifier | Forces focus ring for documentation previews. |
For Designers
Use the Toggle Selector for binary filtering or view switching where both options are always visible and labeled.
Don't use for on/off settings. Use the Toggle (switch) for boolean state changes.
Keep both labels short and similar in length for balanced visual weight.
Don't use for more than two options. Use a Tab Bar or segmented control for three or more choices.
Spacing Guidance
| Context | Property | Value | Token |
|---|---|---|---|
| Selector to adjacent content | margin-bottom | 16-24px | --space-200 to --space-300 |
| Selector within a filter bar | gap | 16px | --space-200 |
| Selector to heading above | margin-top | 8px | --space-100 |
Usage Examples
Basic Toggle Selector (Mobile)
<div class="toggle-selector" role="radiogroup" aria-label="Search method">
<span class="toggle-selector-pill"></span>
<button class="toggle-selector-option is-active" type="button"
role="radio" aria-checked="true">
Search by Details
</button>
<button class="toggle-selector-option" type="button"
role="radio" aria-checked="false">
Search by Need
</button>
</div>
Desktop Variant (Right Active)
<div class="toggle-selector toggle-selector-desktop right-active"
role="radiogroup" aria-label="Search method">
<span class="toggle-selector-pill"></span>
<button class="toggle-selector-option" type="button"
role="radio" aria-checked="false">
Search by Details
</button>
<button class="toggle-selector-option is-active" type="button"
role="radio" aria-checked="true">
Search by Need
</button>
</div>
JavaScript Initialization
document.querySelectorAll('.toggle-selector').forEach(selector => {
const options = selector.querySelectorAll('.toggle-selector-option');
options.forEach((opt, i) => {
opt.addEventListener('click', () => {
options.forEach(o => {
o.classList.remove('is-active');
o.setAttribute('aria-checked', 'false');
});
opt.classList.add('is-active');
opt.setAttribute('aria-checked', 'true');
selector.classList.toggle('right-active', i === 1);
});
});
});
Filter
A composed pattern for refining search results: a mode toggle (Details vs Need), optional filter controls, and primary actions. Mobile uses a compact bar with an expand affordance; desktop lays out criteria in a horizontal row.
Overview
The Filter pattern combines existing system components: the Toggle Selector (Search by Details / Search by Need), Buttons (outline and primary icon-only for filter open, primary icon-only for close, text buttons for Clear all and Close), and one Selector Accordion per criterion when Search by Details is active. Search by Need swaps the criteria region for a Button Group (.btn-group / .btn-group-item with .btn-group-icon), not accordions. Omit the optional info icon on Details criterion labels unless a specific field exposes contextual help.
Mobile (Figma size S, 336px max width): 40px toggle height, 14px bold labels on the selector, filter icon in an outline icon-only button when the panel is collapsed. Expanded state shows the same toggle, a primary icon-only close control, stacked criteria, and a row with Clear all (disabled when no changes) and Close.
Desktop (Figma size M): 46px toggle height, 16px bold labels on the selector, criteria in a horizontal row (narrow columns), and Clear all aligned to the trailing edge. Spacing follows jumper and padding tokens from the file.
Figma source: WV Design System 1.02 — Drop-down filter menu (component set; node 16318:5349).
Breakpoint: The desktop layout transitions to the mobile layout at 1103px (tablet max). See the Working Example below.
Variants
Mobile — Collapsed
Toggle selector plus outline filter icon. Full width is capped at --card-s (336px).
Mobile — Expanded (Details mode)
Close control replaces the filter icon. Criteria stack vertically with --padding-m between blocks.
Mobile — Expanded (Search by Need, "Waiting" selected)
The default state for Search by Need pre-selects "Waiting over 1 year" and shows its Snackbar. The Button Group stacks vertically with 1px bottom borders. The footer has a single "Close" button (btn-outline). Figma state=expanded, filter=need waiting, status=selected, viewport=mobile.
Desktop — Details mode
Toggle centered above a criteria row sized to the Figma content width (1057px inside the 1105px frame). Criterion columns use flex: 1 with min-width: 128px on the accordion trigger so 128px is a minimum, not a fixed width. Gaps use --jumper-XS-XXS (24px). Clear all uses intrinsic button width; extra horizontal space goes to the criteria columns. Top padding --jumper-S-XS (32px) aligns the button with the triggers.
Desktop — Search by Need ("Waiting" selected) mode
The default state for Search by Need pre-selects "Waiting over 1 year." The Button Group shows the selected item at its intrinsic width (not stretched) and a Snackbar appears below it with descriptive text. All four icons use the action color (--btn-icon-action). The Button Group and Snackbar are grouped with a --jumper-XXS-XXXS (16px) gap. Figma state=expanded, filter=need waiting, viewport=desktop.
Working Example
Interactive demo. Toggle between Details and Need; select accordion options or need buttons. Resize the browser below 1104px to see the mobile layout.
Accessibility Considerations
WCAG 2.1 AA target. The pattern exposes multiple controls; each interactive element must have an accessible name.
Keyboard and focus
- Tab through the toggle options (buttons), filter/close icon buttons, each Selector Accordion category header and option, and footer actions in DOM order.
- Toggle selector options should use
role="radio"andaria-checked, or be implemented as a singletablistif you adopt that variant from the Toggle Selector page. - When Search by Details is active, each criterion uses the Selector Accordion disclosure pattern: category
<button>witharia-expandedandaria-controls, options as<button>rows witharia-selectedon the current value (see that component page). - When Search by Need is active, need criteria use the Button Group (
.btn-group/.btn-group-item); follow that page for keyboard and selection semantics when you wire behavior.
ARIA
- Wrap the panel in
role="region"witharia-label(e.g. "Search filters") when it is a distinct landmark. - Icon-only buttons require
aria-label(e.g. "Open filters", "Close filters").
Construction
Layout tokens
| Property | Mobile | Desktop |
|---|---|---|
| Panel max width | --card-s (336px) | 1105px (Figma frame) |
| Panel padding | --padding-m (16px) | --jumper-XS-XXS (24px) |
| Panel border | 1px solid var(--border-default) | " |
| Panel radius | --radius-m | " |
| Header gap (toggle to icon button) | --padding-m | " |
| Body gap (stack / row) | --padding-m | --jumper-XS-XXS |
| Label to category (Selector Accordion) | --jumper-XXXS-Tiny (8px), per Selector Accordion spec | " |
| Desktop criteria row | — | Max width 1057px; five criterion columns flex: 1 1 0 with min-width: 128px; accordion category min-width: 128px, width: 100%; gap --jumper-XS-XXS |
| Desktop Clear all | — | flex: 0 0 auto; button intrinsic width; column padding-top: var(--jumper-S-XS) (32px) |
| Responsive breakpoint | max-width: 743px; (--bp-mobile-max) | min-width: 1104px (--bp-desktop-min) |
| Shadow (Snackbar) | var(--shadow-default): 0px 8px 16px 0px with color --shadow-default-container (light: rgba(179,179,179,0.1), dark: rgba(24,24,24,0.1)) | " |
| Need Snackbar width | 100% (full width) | --space-9200 (736px) |
Components used
| Part | Implementation |
|---|---|
| Mode switch | .toggle-selector + .toggle-selector-desktop on desktop; widths 260px / 310px per Toggle Selector spec. |
| Open / Close | .btn.btn-outline.btn-icon-only.btn-s with fa-filter; .btn.btn-primary.btn-icon-only.btn-s with fa-xmark. |
| Details criterion row | Selector Accordion: .sel-accordion-group, .sel-accordion-label, .sel-accordion-stack (category + .sel-accordion-dropdown). |
| Need criteria | Button Group: .btn-group, .btn-group-item, .btn-group-icon. Mobile: stacked column with 1px bottom borders. Desktop: intrinsic-width horizontal row (not stretched). All icons use --btn-icon-action; selected items inherit the Button Group .selected state colors. |
| Need expanded (Snackbar) | When a need option is selected, a Snackbar (.snackbar) appears below the Button Group with descriptive text. Grouped with --jumper-XXS-XXXS gap. Desktop width: --space-9200 (736px). Mobile: full width. |
| Actions | .btn.btn-primary.btn-m; Clear all disabled per spec when no dirty filters. |
Application and Context
For Developers
| Class / region | Role |
|---|---|
.doc-wide | Allows an element to expand beyond the default 736px body column up to 80vw. Use on preview blocks, tables, or images that need more horizontal space. |
.pattern-filter | Root panel; add .pattern-filter-mobile or .pattern-filter-desktop. |
.pattern-filter-mobile-expanded | Applies vertical gap between header and body on mobile expanded state. |
.pattern-filter-header | Row: toggle + trailing icon button. |
.pattern-filter-body | Criteria and optional CTA row / column. |
.pattern-filter-field | Wraps one .sel-accordion-group per criterion (spacing between fields uses body gap tokens above). |
.sel-accordion-demo | Optional (docs site): enables expand/collapse and option selection in the demo shell, same as the Selector Accordion page. |
| Criterion label icon | Do not add .label-icon on filter criteria by default (matches Figma). Use it only when the product implements an active info control for that field. |
.pattern-filter-cta-row | Mobile footer buttons (flex row, wrap). |
.pattern-filter-cta | Desktop trailing column for Clear all. |
.toggle-selector-demo | Optional: enables existing doc-site JS for pill animation (same as Toggle Selector page). |
.pattern-filter-body--need | Applied to .pattern-filter-body when "Search by Need" is active. Centers the Button Group on desktop, stacks vertically on mobile. Groups button group + Snackbar with --jumper-XXS-XXXS gap. |
[data-filter-demo] | Working Example wiring: enables toggle switching (Details/Need), need button selection, Snackbar display, and Clear all logic via app.js. |
[data-filter-mode] | On toggle buttons inside [data-filter-demo]. Values: details or need. Toggles visibility of the corresponding [data-filter-body]. |
[data-need] | On .btn-group-item buttons. Values: waiting, orphan, risk, hunger. Click selects/deselects and shows the corresponding Snackbar description. |
For Designers
Use the Filter pattern when users need to narrow a dataset (e.g. sponsorship search) without leaving the current view.
Don't use this pattern for linear step-by-step flows; use the Tab Bar or a dedicated wizard.
Keep criterion labels short and match typography to Input labels (Body M).
Don't invent new field chrome; stay on --border-default and shared radii for triggers.
Usage Examples
Desktop markup
At viewport widths of 1104px and above, the filter uses .pattern-filter-desktop. The toggle selector sits at the top, followed by a horizontal row of accordion fields and a "Clear all" button. When "Search by Need" is active, the body switches to a centered button group with a Snackbar.
<div class="pattern-filter pattern-filter-desktop"
role="region" aria-label="Filters">
<div class="toggle-selector toggle-selector-desktop" style="width:310px"
role="radiogroup" aria-label="Search mode">
<span class="toggle-selector-pill"></span>
<button type="button" class="toggle-selector-option is-active"
role="radio" aria-checked="true">Search by Details</button>
<button type="button" class="toggle-selector-option"
role="radio" aria-checked="false">Search by Need</button>
</div>
<!-- Search by Details body -->
<div class="pattern-filter-body">
<div class="pattern-filter-field">
<div class="sel-accordion-group">
<div class="sel-accordion-label" id="loc-lbl">Location</div>
<div class="sel-accordion-stack">
<button type="button" class="sel-accordion-category"
aria-expanded="false" aria-controls="loc-opts"
aria-labelledby="loc-lbl">
<span>No preference</span>
<i class="sel-accordion-icon fa-solid fa-chevron-down"
aria-hidden="true"></i>
</button>
<div class="sel-accordion-dropdown is-collapsed" id="loc-opts"
role="group" aria-label="Location options">
<button type="button" class="sel-accordion-option is-selected"
aria-selected="true">No preference</button>
<button type="button" class="sel-accordion-option">Kenya</button>
<button type="button" class="sel-accordion-option">Nigeria</button>
</div>
</div>
</div>
</div>
<!-- repeat .pattern-filter-field for Gender, Age, Birth Month, Birth Day -->
<div class="pattern-filter-cta">
<button type="button" class="btn btn-primary btn-m is-disabled"
disabled aria-disabled="true">
<span class="btn-label">Clear all</span>
</button>
</div>
</div>
<!-- Search by Need body (hidden by default) -->
<div class="pattern-filter-body pattern-filter-body--need"
style="display:none">
<div class="btn-group" role="group" aria-label="Search by Need">
<button type="button" class="btn-group-item selected"
data-need="waiting">
<i class="btn-group-icon fa-solid fa-calendar"
aria-hidden="true"></i>
Waiting over 1 year
</button>
<button type="button" class="btn-group-item" data-need="orphan">
<i class="btn-group-icon fa-solid fa-child"
aria-hidden="true"></i>
Orphaned child
</button>
<button type="button" class="btn-group-item" data-need="risk">
<i class="btn-group-icon fa-solid fa-circle-exclamation"
aria-hidden="true"></i>
Higher-risk area
</button>
<button type="button" class="btn-group-item" data-need="hunger">
<i class="btn-group-icon fa-solid fa-bowl-rice"
aria-hidden="true"></i>
Hunger crisis area
</button>
</div>
<div class="snackbar">
<div class="snackbar-content">
<div class="snackbar-icon-text">
<div class="snackbar-text">
<p class="snackbar-body">Children who have been waiting
for a sponsor for 12 months or more.</p>
</div>
</div>
</div>
</div>
</div>
</div>
Mobile markup
At viewport widths of 1103px and below, the filter uses .pattern-filter-mobile. A header row holds the toggle selector (260px) and a collapse/expand icon button. The body stacks fields vertically and hides when collapsed.
<div class="pattern-filter pattern-filter-mobile pattern-filter-expanded"
role="region" aria-label="Filters">
<div class="pattern-filter-header">
<div class="toggle-selector" style="width:260px"
role="radiogroup" aria-label="Search mode">
<span class="toggle-selector-pill"></span>
<button type="button" class="toggle-selector-option is-active"
role="radio" aria-checked="true">Search by Details</button>
<button type="button" class="toggle-selector-option"
role="radio" aria-checked="false">Search by Need</button>
</div>
<button type="button" class="btn btn-primary btn-icon-only btn-s"
aria-label="Close filters">
<i class="btn-icon fa-solid fa-xmark" aria-hidden="true"></i>
</button>
</div>
<!-- Search by Details body -->
<div class="pattern-filter-body">
<div class="pattern-filter-field">
<div class="sel-accordion-group">
<div class="sel-accordion-label" id="m-loc-lbl">Location</div>
<div class="sel-accordion-stack">
<button type="button" class="sel-accordion-category"
aria-expanded="false" aria-controls="m-loc-opts"
aria-labelledby="m-loc-lbl">
<span>No preference</span>
<i class="sel-accordion-icon fa-solid fa-chevron-down"
aria-hidden="true"></i>
</button>
<div class="sel-accordion-dropdown is-collapsed" id="m-loc-opts"
role="group" aria-label="Location options">
<button type="button" class="sel-accordion-option is-selected"
aria-selected="true">No preference</button>
<button type="button" class="sel-accordion-option">Kenya</button>
<button type="button" class="sel-accordion-option">Nigeria</button>
</div>
</div>
</div>
</div>
<!-- repeat .pattern-filter-field for Gender, Age, Birth Month, Birth Day -->
</div>
<!-- Search by Need body (hidden by default) -->
<div class="pattern-filter-body pattern-filter-body--need"
style="display:none">
<div class="btn-group" role="group" aria-label="Search by Need">
<button type="button" class="btn-group-item selected"
data-need="waiting">
<i class="btn-group-icon fa-solid fa-calendar"
aria-hidden="true"></i>
Waiting over 1 year
</button>
<button type="button" class="btn-group-item" data-need="orphan">
<i class="btn-group-icon fa-solid fa-child"
aria-hidden="true"></i>
Orphaned child
</button>
<button type="button" class="btn-group-item" data-need="risk">
<i class="btn-group-icon fa-solid fa-circle-exclamation"
aria-hidden="true"></i>
Higher-risk area
</button>
<button type="button" class="btn-group-item" data-need="hunger">
<i class="btn-group-icon fa-solid fa-bowl-rice"
aria-hidden="true"></i>
Hunger crisis area
</button>
</div>
<div class="snackbar">
<div class="snackbar-content">
<div class="snackbar-icon-text">
<div class="snackbar-text">
<p class="snackbar-body">Children who have been waiting
for a sponsor for 12 months or more.</p>
</div>
</div>
</div>
</div>
</div>
</div>
Responsive markup (recommended)
Use .pattern-filter-responsive instead of viewport-specific classes. A single markup auto-switches between desktop and mobile layouts at the 1103px breakpoint via CSS media queries.
<div class="pattern-filter pattern-filter-responsive pattern-filter-expanded"
role="region" aria-label="Filters" data-filter-demo>
<!-- Mobile header (hidden at >= 1104px) -->
<div class="pattern-filter-header">
<div class="toggle-selector" role="radiogroup"
aria-label="Search mode">
<span class="toggle-selector-pill"></span>
<button type="button" class="toggle-selector-option is-active"
role="radio" aria-checked="true"
data-filter-mode="details">Search by Details</button>
<button type="button" class="toggle-selector-option"
role="radio" aria-checked="false"
data-filter-mode="need">Search by Need</button>
</div>
<button type="button" class="btn btn-primary btn-icon-only btn-s"
aria-label="Close filters" data-filter-collapse>
<i class="btn-icon fa-solid fa-xmark" aria-hidden="true"></i>
</button>
</div>
<!-- Desktop toggle (hidden at <= 1103px) -->
<div class="pattern-filter-toggle-desktop">
<div class="toggle-selector" role="radiogroup"
aria-label="Search mode">
<span class="toggle-selector-pill"></span>
<button type="button" class="toggle-selector-option is-active"
role="radio" aria-checked="true"
data-filter-mode="details">Search by Details</button>
<button type="button" class="toggle-selector-option"
role="radio" aria-checked="false"
data-filter-mode="need">Search by Need</button>
</div>
</div>
<!-- Details body -->
<div class="pattern-filter-body" data-filter-body="details">
<div class="pattern-filter-field">
<div class="sel-accordion-group"><!-- accordion --></div>
</div>
<!-- repeat .pattern-filter-field -->
<div class="pattern-filter-cta">
<button type="button" class="btn btn-primary btn-m is-disabled"
disabled aria-disabled="true">
<span class="btn-label">Clear all</span></button>
</div>
</div>
<!-- Need body -->
<div class="pattern-filter-body pattern-filter-body--need"
data-filter-body="need" style="display:none">
<div class="btn-group" role="group" aria-label="Search by Need">
<button type="button" class="btn-group-item"
data-need="waiting">
<i class="btn-group-icon fa-solid fa-calendar"
aria-hidden="true"></i>
Waiting over 1 year
</button>
<!-- repeat for orphan, risk, hunger -->
</div>
<div class="snackbar" data-need-snackbar
style="display:none"><!-- snackbar content --></div>
</div>
</div>
Key CSS breakpoints
/* Mobile / Tablet */
@media (max-width: 1103px) {
.pattern-filter-responsive .pattern-filter-toggle-desktop { display: none; }
.pattern-filter-responsive .pattern-filter-header { display: flex; }
.pattern-filter-responsive .pattern-filter-body { display: none; }
.pattern-filter-responsive.pattern-filter-expanded .pattern-filter-body {
display: flex;
}
}
/* Desktop */
@media (min-width: 1104px) {
.pattern-filter-responsive .pattern-filter-header { display: none; }
.pattern-filter-responsive .pattern-filter-toggle-desktop { display: flex; }
.pattern-filter-responsive .pattern-filter-body {
display: flex; flex-direction: row;
}
.pattern-filter-responsive .pattern-filter-body.pattern-filter-body--need {
flex-direction: column; align-items: center;
}
}
Initialize demos (optional, docs site)
Add toggle-selector-demo on the toggle for pill animation (see Toggle Selector). Add sel-accordion-demo on each .sel-accordion-group for expand/collapse and option selection (see Selector Accordion). Add data-filter-demo on the root .pattern-filter to wire up mode toggling and need selection.
Forms
Reusable layout patterns for collecting user input consistently across the system.
Overview
Form patterns standardize how input groups, labels, validation messages, and submit actions are composed. The system provides five form primitives: Input (single-line text field), Text Area (multi-line), Checkbox, Radio Button, and Toggle Switch. Each primitive uses shared tokens for spacing, typography, and color to maintain visual consistency across any form layout.
Variants
Label
The label component pairs a text label with an optional trailing info icon. A required variant places a red asterisk to the left of the label for accessibility (not to the right, per WCAG best practice).
Input Field
A single-line text field with optional leading icon, trailing status icon, label, and hint text. Supports four validation states.
Input Validation States
Validation feedback colors the field border and hint text. The trailing icon changes to indicate status.
Text Area
A multi-line field for longer content. Includes a label block with optional hint copy above the field. Minimum height is 224px.
Checkbox
A binary selection control. The checked state uses the action color with a white check icon. Label text sits to the right with 24px gap.
Radio Button
A single-choice control within a group. The selected state fills with the action color and shows a white dot. Label text sits to the right with 24px gap.
Toggle Switch
A binary on/off control. The track fills with the action color when on. The knob slides left (off) to right (on). 64px wide, 32px tall, 24px knob.
Accessibility Considerations
WCAG 2.1 AA target. All form controls must be programmatically associated with their labels.
Keyboard Interactions
| Key | Action |
|---|---|
Tab | Moves focus between form controls in DOM order. |
Space | Toggles checkbox and toggle switch. Selects focused radio button. |
Enter | Submits the form when a button has focus. |
Arrow Up/Down | Cycles through radio buttons within a group. |
ARIA and Semantics
- Use native
<label>withforattribute or wrap the control inside the label. - Error messages must use
aria-describedbypointing to the hint element, plusaria-invalid="true"on the field. - Radio groups should be wrapped in a
<fieldset>with a<legend>. - Toggle switches use
role="switch"witharia-checked. - Disabled fields use the native
disabledattribute (removes from tab order).
Focus Management
- Input focus ring: 2px solid
--border-action(#f78958), inset -5px, radius 10px (slightly larger than the field radius). - Checkbox and radio focus: 2px solid outline at 2px offset.
- Toggle focus: 2px solid outline around the track at 2px offset.
Construction
Scale and Spacing
| Property | Value | Token |
|---|---|---|
| Field padding (vertical) | 8px | Fixed/padding/S |
| Field padding (horizontal) | 12px | Fixed/padding/M-S |
| Field min-width | 336px | Fixed |
| Field border radius | 8px | Corner Radius/M field |
| Field border width | 1px | Border/Width/M default |
| Icon-to-text gap | 8px | Fixed/padding/S |
| Label-to-field gap | 8px | Fixed/padding/S |
| Checkbox/Radio-to-label gap | 24px | Fixed/padding/L |
| Toggle track | 64 x 32px | Fixed |
| Toggle knob | 24px circle | Fixed |
| Radio dot | 8px circle | Fixed |
| Text area min-height | 224px | Fixed |
Styles
| Property | Value | Token |
|---|---|---|
| Label font | Lato Regular 16px/26px | Body/Body M |
| Field text | Lato Regular 16px/26px | Body/Body M |
| Hint text | Lato Regular 14px/18px | Body/Body S |
| Icon font | FA6 Free Solid 16px | Body/Icons/M icon |
| Checkbox radius | 4px | Corner Radius/S checkbox |
| Radio / Toggle radius | 104px (full round) | Corner Radius/XL round |
Color Tokens
| State | Background | Border | Text / Icon |
|---|---|---|---|
| Default | Surface/Global/default container#fdfdfd |
Border/Global/accent#d0d0d0 |
Text/Default/body#464646 |
| Hover | Surface/Global/default container#fdfdfd |
Border/Action/action-hover#ffa984 |
Text/Action/action-hover#f78958 |
| Focus | Surface/Global/default container#fdfdfd |
Border/Action/action#f78958 |
Text/Default/body#464646 |
| Disabled | Surface/Button/Primary/bg-disabled#f5f5f5 |
Border/Action/disabled#e9e9e9 |
Text/Action/action-disabled#979797 |
| Error | Surface/Global/default container#fdfdfd |
Border/Status/error#f88c90 |
Text/Status/error#de515e |
| Success | Surface/Global/default container#fdfdfd |
Border/Status/success#609c71 |
Text/Status/success#438558 |
| Information | Surface/Global/default container#fdfdfd |
Border/Status/information#3a90a9 |
Text/Status/information#097892 |
| Checked / On | Surface/Action/action#e96620 |
Border/Action/action#f78958 |
White (#ffffff) |
Application and Context
For Developers
| Class / Attribute | Type | Description |
|---|---|---|
.input-group | Container | Wraps label, field, and hint into a vertical stack (8px label-to-field, 4px field-to-hint). |
.input-label | Label | Horizontal flex row for label text and optional info icon. |
.label-required | Modifier | Added to .input-label. Renders a red asterisk to the left of the label via ::before. |
.label-icon | Icon | Trailing info icon inside .input-label. Uses Font Awesome 6 Solid. |
.form-field | Field wrapper | The input container with icons, border, and background. Wrap a native <input> inside. |
.field-error / .field-success / .field-information | Modifier | Applied to .form-field to set status border color. |
.input-hint | Hint text | Below the field. Add .hint-error, .hint-success, or .hint-information for status color. |
.form-textarea | Textarea | Native <textarea> element styled to match the field design. |
.form-checkbox | Label wrapper | Wraps a native <input type="checkbox"> and its label text. |
.form-radio | Label wrapper | Wraps a native <input type="radio"> and its label text. |
.form-toggle | Label wrapper | Wraps a hidden checkbox, .toggle-track (with .toggle-knob), and label text. |
For Designers
Stack fields vertically with var(--space-300) gap. Group related fields inside a <fieldset>.
Don't place fields in multiple columns on mobile. Horizontal layouts only work at desktop widths with sufficient space.
Place the primary action button aligned to the left, below the last field, with 24px spacing.
Don't rely solely on color to communicate validation state. Pair error/success border color with descriptive hint text.
Spacing Guidance
| Context | Spacing | Token |
|---|---|---|
| Between stacked fields | 24px | --space-300 |
| Label to field | 8px | --padding-s |
| Field to hint text | 4px | --padding-xs |
| Last field to submit button | 24px | --space-300 |
| Checkbox/Radio vertical stack | 16px | --space-200 |
Usage Examples
Required Label
<label class="input-label label-required" for="fname">
First name
<i class="label-icon fa-solid fa-circle-question"></i>
</label>
Input with Label and Hint
<div class="input-group">
<label class="input-label" for="fname">
First name
<i class="label-icon fa-solid fa-circle-question"></i>
</label>
<div class="form-field">
<i class="field-icon fa-solid fa-user"></i>
<input type="text" id="fname" placeholder="First name">
<i class="field-icon fa-solid fa-circle-xmark"></i>
</div>
<span class="input-hint">Hint text</span>
</div>
Input with Error
<div class="input-group">
<label class="input-label" for="email">Email</label>
<div class="form-field field-error">
<i class="field-icon fa-solid fa-envelope"></i>
<input type="email" id="email" placeholder="Email"
aria-invalid="true" aria-describedby="email-err">
<i class="field-icon field-status-icon
fa-solid fa-circle-xmark"></i>
</div>
<span class="input-hint hint-error"
id="email-err">Please enter a valid email.</span>
</div>
Text Area
<div class="input-group">
<label class="input-label" for="bio">Bio</label>
<span class="input-hint"
style="color:var(--text-placeholder)">Hint copy</span>
<textarea class="form-textarea" id="bio"
placeholder="Tell us about yourself"></textarea>
</div>
Checkbox Group
<fieldset>
<legend>Preferences</legend>
<label class="form-checkbox">
<input type="checkbox" checked>
<span>Email notifications</span>
</label>
<label class="form-checkbox">
<input type="checkbox">
<span>SMS notifications</span>
</label>
</fieldset>
Radio Group
<fieldset>
<legend>Size</legend>
<label class="form-radio">
<input type="radio" name="size" value="s">
<span>Small</span>
</label>
<label class="form-radio">
<input type="radio" name="size" value="m" checked>
<span>Medium</span>
</label>
<label class="form-radio">
<input type="radio" name="size" value="l">
<span>Large</span>
</label>
</fieldset>
Toggle Switch
<label class="form-toggle">
<input type="checkbox" role="switch"
aria-checked="true" checked>
<span class="toggle-track">
<span class="toggle-knob"></span>
</span>
<span>Dark mode</span>
</label>
Design Example
A prayer request card demonstrating form layout at Desktop and Mobile breakpoints. The card uses jumper spacing tokens so padding, gaps, and margins adapt responsively.
Desktop (736px)
We're here to pray for you
Let's walk this journey of hope, together.
Mobile (384px)
We're here to pray for you
Let's walk this journey of hope, together.
Accessibility
Every component and pattern in this system targets WCAG 2.1 AA compliance as a baseline.
Color Contrast
- Normal text: minimum 4.5:1 contrast ratio against its background.
- Large text (24px+ regular, 18.66px+ bold): minimum 3:1.
- Non-text elements (icons, borders): minimum 3:1.
Keyboard Navigation
- All interactive elements must be reachable via
Tab. - Focus indicators must be visible. This system uses a 2px solid outline in
--surface-action. - Modal components must trap focus while open.
Screen Reader Support
- Use semantic HTML elements first (
<button>,<nav>,<main>). - Apply ARIA roles only when native semantics are insufficient.
- Dynamic content changes must use
aria-liveregions.
Disabled States
Disabled elements use --text-action-disabled and --surface-disabled. While contrast is intentionally reduced, the element must remain legible.
Responsive Design
The system targets three breakpoints: Mobile (384px), Tablet (800px), and Desktop (1504px). A minimum mobile reference of 320px is provided for designers but is not enforced as a development breakpoint.
Device Sizes and Breakpoints
| Device | Viewport / Frame size (px) | Min width breakpoint (px) | Max width breakpoint (px) |
|---|---|---|---|
| Mobile | 384 | 320 | 743 |
| Tablet | 800 | 640 | 1151 |
| Desktop | 1504 | 1152 | 1919 |
| Mega | 1920 | ... | ... |
Approach
- Primary design focus: Mobile and Desktop.
- Tablet values are defined only where they deviate from Mobile.
- Typography, jumper spacing, grid, and margins all adapt per breakpoint.
Grid System
| Property | Desktop | Tablet | Mobile |
|---|---|---|---|
| Columns | 12 | 8 | 4 |
| Col Width | 64px | 48px | 72px |
| Col Gutter | 32px | 32px | 16px |
| Col Margin | 16px | 16px | 8px |
| Global Margin | 176px | 80px | 16px |
| Row Gap | 32px | 16px | 16px |
| Body Max Width | 736px | 448px | 336px |
Fixed vs Fluid Layouts
Fixed layout: Content does not expand when the viewport stretches beyond the design frame (1504px). The total side margin is Global Margin + Col Margin (e.g., 176 + 16 = 192px per side on desktop).
Fluid layout: Content extends to the edges of the outer columns. The margin consists of only the Global Margin, and containers stretch left and right as the viewport grows.
Readability
Body text (Body M, 16px) should be constrained to 60-75 characters per line. The grid's Body text.max width token (736px on Desktop, 448px on Tablet, 336px on Mobile) enforces this.