Skip to content

Draft · v0.1

Action hierarchy

How buttons share a screen. One action leads; the rest support it. This page turns the unwritten calls we already make into rules the whole team, and the AI, can follow.

One primary action per screen. Everything else is secondary or a text link. Yellow is what you do next; green is what is already true. They never compete on the same screen. In dark modes, brand/action resolves to lime automatically.

The DS Button has three types. One Primary (the yellow CTA) per screen; everything else supports it. Pick the lowest-emphasis type that still reads as the right importance.

TypeExampleColor tokenFlutter (M3)
PrimarySavebrand/action. Yellow → limeFilledButton
SecondaryNot nowbutton/secondary, neutral tonal. No longer greenFilledButton.tonal ¹
TertiarySkipbutton/tertiary = surface, drawn by stroke/strongOutlinedButton

Always radius/full (pill), Inter Bold 16/24, no icon by default. In Light all three labels are black (onPrimary, onSecondary, onTertiary); in Carbon and Navy they lighten with the mode. White on green only ever reached 3.24:1, which is part of why green left the buttons in the first place.

Only one button is ever coloured, and it is always yellow

Section titled “Only one button is ever coloured, and it is always yellow”

Yellow is what you do next. Green is what is already true. Since August 2026 green is not a button at all.

If a screen has a commit, that commit is Primary yellow, whether it is “Go to Stellar Plan” or “Save”. Anything that accompanies it drops to Secondary (neutral tonal) or to a text link if it is an escape. Two coloured pills on one screen means the hierarchy was never decided.

Count the yellow on any screen and the answer must be 0 or 1. Zero is a perfectly legal screen: a list of settings or a navigation screen has no commit and needs no CTA.

Yellow is rationed by position, not by importance. It is not a reward you save for the big moments. If you ration it by importance, ordinary screens lose their primary, degrade into a wall of neutral buttons, and nobody can tell where the commit is.

Two actions that belong together stack: the yellow on top, the escape as a centered text link underneath. Never two solid pills side by side.

Do

  • One yellow, or none. Continue alone, with Not now or a text link beside it.
  • Green for what is already true. Selected borders, feature checks, the active nav destination, positive amounts, chevrons.
  • Green for what leads somewhere. An actionable line icon, a link to real content.

Don’t

  • A green button. Not as a confirm, not as a “continue”, not as the second action beside a yellow one.
  • Two coloured pills on one screen. If two actions belong together they stack, and only the top one is coloured.
  • Saving yellow for the big moments. An ordinary screen with a commit still gets its yellow.

The reason is a tense, and it is worth carrying in your head because it decides every future case on its own.

Green already means “already true” in nine different places: a selected plan, a feature check, the active nav destination, an accent word, a positive amount, a chevron, a spinner, progress, a link. A button means “about to become true”. Those are opposite tenses, and asking one colour to hold both is how green emptied out in the legacy app the first time.

So green did not lose a job here. It kept all nine and gave up the one that contradicted them.

Green is not being rationed for scarcity. It is on nearly every screen in the product already, doing work no button could add to. Full rationale in ADR-013.

¹ Which native widget Secondary and Tertiary map to is still open with Kevin.

A secondary button never appears alone. It exists to support a primary. If a screen has only one action, that action is primary, full stop. When two actions sit together, the affirmative one is primary and the supporting one drops to secondary or text.

Use secondary when

  • It sits beside a primary action (Cancel next to Save).
  • The action is reversible or non-committal.
  • You need a second action visible but clearly lower.

Don’t use secondary when

  • It’s the only action. Promote it to primary.
  • To fit a second “important” action. Rank them instead.
  • For navigation. Use a text link.

Never place two high-emphasis buttons side by side. The eye can’t tell which one leads. Pair one primary with one secondary (or text). On the 360×667 floor, if two actions don’t fit on one row, stack them: primary on top.

Do
CancelSave
One primary (action color) leads; the supporting action drops to Tertiary.
Don’t
DiscardSave
Two primaries compete. Nothing leads.

Dialogs take at most two actions, both text buttons. Cancel sits left, confirm right (confirm is always last). When stacked on a narrow screen, confirm goes on top. A destructive confirm keeps the affirmative slot but takes its color from feedback/error. Don’t invent a new token.

The parity contract. The same tokens Kevin consumes in Flutter. Values resolve per mode.

brand/actionbrand/emphasisfeedback/errorbutton/tertiaryradius/fullspacing/8