Design System

Content & feedback

Cards for grouping content, feedback surfaces for status and progress, and shared empty/error/loading states — all built from the core primitives.

Card

Container for grouped content. Non-interactive by default; add `interactive` for hover/active/focus affordances, and `asChild` to render a real link.

import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, CardMedia } from "@/design-system";

Variants

Surface

Flat, no border.

Outlined

Subtle border.

Elevated

Soft shadow.

Muted

Recessed background.

States

Interactive

Hover and focus affordances.

Selected

Chosen in a multi-select group.

Disabled

Not available right now.

Interactive card as a link

Tab to it, then hover with the pointer.

Full composition with media

Team collaborating around a laptop

Quarterly planning

Shared with 8 people

Review Q3 goals and lock the roadmap before Friday's stand-up.
In review

Invoice #4821

Due 14 June 2025

£2,450.00

Accessibility

  • Renders a plain <div> by default; asChild swaps in the child element (e.g. an <a>) while keeping card styling.
  • Interactive cards get a visible focus ring and are reachable by keyboard when rendered as a link or button.
  • CardMedia always requires meaningful alt text (or an empty string for decorative images).

Alert

Block-level status message with an icon, title and optional description, and an optional dismiss control.

import { Alert } from "@/design-system";

Variants

Maintenance window

Planned downtime Saturday 01:00–02:00 UTC.

New feature available

Try the redesigned reporting dashboard.

Payment received

Invoice #4821 has been marked as paid.

With action

Solid appearance with dismiss

Changes published

Your changes are now live for all users.

Accessibility

  • Error and warning alerts use role="alert"; others use role="status".
  • Status is carried by the icon and title text, never colour alone.
  • Dismiss uses IconButton, which always has an accessible name.

Callout

Quieter inline emphasis for a sentence or two within body content, sharing the same status vocabulary as Alert.

import { Callout } from "@/design-system";

Variants

Invoices are generated automatically on the 1st of each month.
Your trial ends in 3 days — add a payment method to keep access.
This action cannot be undone once confirmed.

With title and action

Two-factor authentication is on

Recovery codes were generated when you enabled it.

Note

Exports include archived records by default.

Accessibility

  • Icon and coloured border reinforce the status; text always carries the meaning.

Toast

Transient notification stacked in a fixed viewport. ToastProvider owns state; useToast() triggers toasts imperatively.

import { ToastProvider, useToast, Toast } from "@/design-system";

Trigger toasts

Static anatomy

Reminder

Stand-up starts in 10 minutes.

Accessibility

  • Each toast sets role="status" or role="alert" with a matching aria-live politeness.
  • Auto-dismiss pauses on hover and keyboard focus, then resumes with the remaining time.
  • The dismiss control is a labelled IconButton.

Notification

List-style item for a notification feed, with a leading avatar/icon, title, description and timestamp.

import { Notification } from "@/design-system";

Feed

PS

Priya Shah commented on your document

“Can we push the launch to Thursday?”

2 minutes ago

Unread

New team member joined

Alex Chen accepted their invite.

1 hour ago

Read

New message in #general

Yesterday

Read

Avatar and action

AC

Alex Chen requested access to Design tokens

Read-only access, expires in 30 days.

10 minutes ago

Unread

Accessibility

  • Unread items set aria-current and combine a dot with bolder text, plus a screen-reader-only "Unread"/"Read" label.

StatusIndicator

A coloured dot paired with a mandatory text label, for compact status display in tables and lists.

import { StatusIndicator } from "@/design-system";

Statuses

OperationalDegradedOutagePausedLive

With icon instead of a dot

OnlinePendingOffline

Accessibility

  • The label prop is always visible text — colour is never the only signal.

Progress

Progress indicates the completion of an ongoing task or process. Use it when users benefit from knowing how much of a task has been completed.

Use determinate progress when the completion percentage is known. Use indeterminate progress when the completion percentage cannot be determined.

Determinate

Use determinate progress when the completion percentage is known.

Uploading report.pdf42%

Indeterminate

Use indeterminate progress when the duration or completion percentage cannot be determined. The animation is suppressed for users who prefer reduced motion.

Processing your request

Sizes

Three track heights for different densities. Match the size to the surrounding content, not to the importance of the task.

Small

Medium

Large

With value

The percentage label is optional and stays visually subordinate to the task description.

With value label

Uploading report.pdf42%

Without value label

Interactive playground

For verification only — keep controls like these out of product surfaces.

Uploading report.pdf42%

Accessibility

  • Use a visible label when the progress represents a specific task.
  • Provide the current value when progress is determinate.
  • Use indeterminate progress when the completion percentage cannot be calculated.
  • Do not rely on colour alone to communicate progress.
  • Maintain sufficient contrast between the indicator and track.
  • Respect prefers-reduced-motion.

Usage

  • Pair progress with a short description of the task that is running.
  • Switch to indeterminate as soon as the remaining work becomes unknown.
  • Don't use progress for instant actions — a spinner or no feedback is calmer.
  • Don't animate a determinate bar backwards; clamp the value instead.

Code

CircularProgress

SVG ring progress indicator for compact spaces, determinate or indeterminate.

import { CircularProgress } from "@/design-system";

Sizes and states

65%
90%

Accessibility

  • Sets role="progressbar" with aria-valuenow/min/max; indeterminate omits the value attribute.

Spinner

Indeterminate loading indicator for short operations. Used inside Button and IconButton, and available standalone.

import { Spinner } from "@/design-system";

Sizes

Tones

Accessibility

  • Exposes role="status" with an accessible label ("Loading" by default).
  • The spin animation is disabled under prefers-reduced-motion.

Skeleton

Placeholder block shown while real content loads, in text, circle and rect variants.

import { Skeleton } from "@/design-system";

Variants

Presets

Accessibility

  • Marked aria-hidden — the loading state itself should be announced by a surrounding LoadingState or aria-busy region.

States

EmptyState, ErrorState, LoadingState, SuccessState and NoResultsState share one internal StateShell: an icon slot, title, description and up to two actions.

import { EmptyState, ErrorState, LoadingState, SuccessState, NoResultsState } from "@/design-system";

Empty

No projects yet

Create your first project to start tracking work.

No results for “onboarding checklist”

Try a different search term or clear your filters.

Error, loading and success

Something went wrong

We couldn't load your billing details.

Fetching your dashboard…

Identity verified

You now have access to all account features.

Loading with skeleton indicator

Loading records…

This usually takes a few seconds.

Error with retry and secondary action

Couldn't load this page

Check your connection, then try again.