Design System

Actions

Actions are where hierarchy matters most. Every screen in this system has exactly one primary action; everything else steps down until it is quiet enough to ignore.

What is this?

The action layer: Button with five weighted variants plus link, IconButton for icon-only controls, and DropdownMenu for overflow. All variants come from CVA maps, so an action's weight is a prop, not a style.

When to use it

  • A person can do something: submit, create, export, delete, continue
  • A repeated row or toolbar control needs to compress to a single icon
  • More than two secondary actions need collecting behind an overflow menu

How to use it

  • Choose weight by importance: primary → secondary → tertiary → ghost
  • Put the primary action last in a row on desktop, first in stacked mobile order
  • Use destructive only for irreversible actions, and confirm them

What to avoid

  • Two primary buttons competing in the same view
  • An icon-only destructive control with no tooltip or confirmation
  • Using a link where a button is needed, or a button where navigation happens

Hierarchy

One primary per view. Everything else steps down.
VariantWeightUse for
primaryHighestThe single most important action on the screen
secondaryHighA close alternative sitting beside the primary
tertiaryMediumToolbar actions, filters, table controls
ghostLowRepeated row actions and dense chrome
destructiveHighest, negativeIrreversible actions, always confirmed
linkInlineNavigation inside a sentence or a card footer

Button

The default action control. Weight, size and loading state are props; the component never needs restyling at the call site.

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

When to use

  • Performing an action: submit, save, create, export
  • Confirming or cancelling inside a dialog
  • Triggering an overlay such as a drawer or dialog

When not to use

  • Navigating to another page — use a Link, or Button asChild wrapping one
  • Toggling a binary setting — use Switch
  • Choosing between a small set of options — use ToggleGroup

Variants

Weight is chosen by importance, never by colour preference.

Sizes

Small in dense toolbars, medium by default, large for single-action pages.

With icons

Leading icon for creation, trailing icon for progression.

States

Loading keeps the label so the button never changes width unexpectedly.

Action row

Primary last on desktop; the group is right-aligned to the content edge.

Accessibility

  • Renders a real <button> so Space and Enter work without extra handlers
  • Focus-visible ring uses the dedicated focus token at 3:1 against its surround
  • ds-target guarantees a 24px minimum pointer target, 44px on coarse pointers
  • Loading state keeps the accessible name and disables activation

IconButton

An icon-only control with a required accessible label and a guaranteed minimum target size. The only sanctioned way to render an action without visible text.

import { IconButton, Tooltip } from "@/design-system";

When to use

  • A repeated action in table rows, cards or list items
  • Dense chrome where a label would dominate: close, collapse, more
  • Toolbar controls whose icon meaning is unambiguous

When not to use

  • A screen's primary action — it needs a visible label
  • An action whose icon needs explaining in more than a few words
  • A one-off destructive action outside a repeated row

Variants

Sizes

Match the size of the control it sits beside.

Accessibility

  • The label prop is required and becomes the control's accessible name
  • Always wrap in a Tooltip so sighted users get the same information
  • Hit area expands to 24px minimum (44px on touch) without changing visual size
  • Icons inside are aria-hidden — the label is the single source of the name

Do & don't

Do

  • Keep one primary action per view; demote the rest.
  • Label actions with a verb and its object: “Create project”, not “Submit”.
  • Confirm destructive actions with an AlertDialog before performing them.
  • Keep action order stable across similar screens so muscle memory works.

Don't

  • Do not restyle a button at the call site to invent a new weight.
  • Do not place a destructive action next to the primary action.
  • Do not disable a button without telling the person what would enable it.
  • Do not use an icon-only button for an action people perform once.

Why it is designed this way

Expressing weight as a variant prop rather than a colour choice is what keeps hierarchy consistent across dozens of screens: reviewers can see “two primaries” in the diff. The required label on IconButton exists because icon-only controls are the most common source of accessibility regressions in dense product UI.