Step-forward link
The only control in the product that points down. It carries a person from one step of the guided reveal to the next, and the next step is further down the same page rather than on another one. Two instances, one screen, and that screen is the one the whole product exists to deliver.
Anatomy
One zone, one label and one glyph, which is what makes it an atom. Everything below is drawn by design/system/components/step-forward-link.css through the same entry point a product screen loads, so this page cannot drift from the screens: there is no second copy of the CSS here to drift from.
.nextthe atom itself: an inline-flex box with a 44px floor, body ink and an underline. Always anawith anhref, because it goes somewhere::afterthe arrow,content: "\2193" / "". The string is the glyph, the empty second half is its alternative textmin-heightreads--tap, the 44px floor. This is a standalone link, so the floor applies; the exemption is for a link inside a running sentencetext-decoration-colorreads--line-containerat rest, so the underline is quieter than the words above it, and--text-actionon hover, where it joins the inkmargin-top12px, its own, because the gap to the block it follows belongs to the link rather than to the reveal step around it
The empty alt slot, and why it is the most useful line in this file
content: "\2193" / "" is a string and then an alternative text, and the alternative is deliberately empty. Without it the arrow is part of the generated content and lands in the accessible name, so a screen reader announces "See what they are, down arrow". The arrow is a picture of the direction, not a word of the sentence, and a person who cannot see it gains nothing from hearing it named. The empty slot is the one place in CSS that can say "decorative" about generated content, and this system says it here and once more on the alert's forward arrow.
How it degrades is worth knowing rather than guessing at. A browser that does not parse the alt slot drops the whole declaration, because an invalid declaration is discarded entirely: the link keeps its label, its underline, its floor and its target, and loses only the arrow. That is the right thing to lose, and it is the reason the arrow is a decoration in the stylesheet rather than a character in the markup, where nothing could have been done about it.
Variants and sizes
The step-forward link has one form, so there is no matrix to draw. Both instances render identically: 13.5px, body ink, a 44px floor, a hairline underline at a 3px offset and a muted arrow 6px after the label. The census measured .next as one of the sixteen families with a single value on every property, at both viewports.
| What could vary, and does not Each empty cell is a prohibition with its reason rather than a form nobody has drawn yet. This control has one job on one screen, and every axis it might grow is an axis that would make it a different component |
Values | Where it stands |
|---|---|---|
The one form .next |
13.5px / 44px ↓ 6px after |
guided-reveal, twice: after the count and after the categories. Both are coloured |
| See what they are | ||
| a direction axis | - | Forbidden with a reason. The arrow points down because the destination is down, on the same page. A right-pointing version would be a link to another screen, which is a different promise, and the product already has one: the alert item's own forward arrow |
| an emphasis axis | - | Forbidden with a reason, and it is the One Voice Rule. Petrol is spent on the primary action, the current selection and the trust line. A filled step-forward link would be a fourth claim, and on the reveal it would compete with the monthly total, which is the largest thing on that screen by decision |
| a size axis | - | Forbidden with a reason. Size is set by the container everywhere in this system, and this control has exactly one container: a reveal step. One container, one size |
When to use it
This is D1 drawn: gradual reveal with a paired action, count first, then the categories, then the total. The decision is that a person meets their subscriptions one step at a time and that every step has its own way on, so that nothing arrives before they asked for it. The link is what "asked for it" looks like. It is small on purpose: the step above it is the event, and the way forward is a quiet offer under it rather than a second thing competing for the same attention.
Its two labels are in voice/docs/microcopy.md and are typed there as links, while every committing action on the same screen is typed as a button: "See what they are", "See the monthly total". Both are second-person Tendd labels naming what will appear, not what the person will do to the interface. The voice round changed the first one from "Show me what they are" for exactly that reason: "Show me" is the person speaking, and the product reserves that voice for the share card alone.
The rule, and the anti-rule
The destination is further down the page a person is already on, and the tap reveals rather than commits. Nothing is saved, nothing is sent, nothing is charged, and nothing is left behind: the two steps above stay where they are and can be read again.
When the step leaves the screen, it is a Button. The reveal itself shows the line: two step-forward links between the three steps, then a filled button at the end, because that one goes to Home. The comment in the markup says why the last one is alone: "A menu of next steps at the emotional peak is a second decision nobody asked for."
It stands on its own line as the action of its step, so the 44px target applies to it in full, and it has it. The census found 21 links across 10 pages that fall under 44px because nobody remembered; this one was written with the floor in it.
A link inside running copy is the quiet inline link, which is exempt from the floor because a 44px box inside a paragraph would tear the line spacing apart. Putting this component there would put a 44px inline-flex box and a downward arrow in the middle of a sentence, and the arrow would be pointing at the rest of the sentence.
Limits
U13, context. ADVANCE. The arrow points down, because the link goes further down the same page rather than to another one, so on hover it advances down by one nudge. Everything in this product that moves travels exactly one --nudge, which is 2px, and nothing anywhere travels two. Hover and release run at --dur-state (150ms) on --ease-arrive; a press runs at --dur-press (90ms), because it answers a finger that is already down. The full language and the live specimens are on Motion; the table with sources is Usage rules.
States
Three states, and they exist because the link is interactive. Each one is a token override rather than a style of its own: no hex and no number appears inside :hover, so the dark theme costs three lines in tokens.css rather than forty edits across the components.
The fourth state does not exist, and its absence is a decision. This is an a with an href to an anchor on the same page, and an anchor has no disabled attribute. A step of the reveal a person may not reach yet is not drawn dim, it is not drawn: the reveal renders the steps it has. Faking it with a class the product does not carry would document something the product cannot do, which is the same defect as inventing a variant.
Rest, live
The three, in both themes
The states are described, not photographed. Founder's decision, 2026-08-11: the snapshot strip went from every page in this kit, 120 images across 18 pages. They were framed as measurements rather than illustrations, and that framing was honest, but the bill came due all session: a value moves, and every picture of it is quietly stale until somebody re-shoots it. A stale picture is worse than no picture, because it looks checked. What is left is what a picture never carried anyway: the state, and the token it reads. The resting component is live and interactive above, so hover it, tab to it, and read the value off the real thing rather than off a photograph of it.
--text-body on the label, --text-muted on the arrow, --line-container under both--text-action, and it takes the label, the arrow and the underline at once. Three declarations, one token--color-focus, 6.23:1 as a line. The ring wraps the whole 44px box, not the text, because the box is the target--color-focus, 6.82:1. Had the pair been left for later this ring would have been the light petrol at 1.9:1: in the file, invisible on screenRetake, and this is the whole recipe, because there is no node Playwright in this repo and a
command that does not run is worse than no command:
serve the repo, then for theme in (none, data-theme="dark" on <html>):
goto /design/kit/step-forward-link.html
move the pointer away -> screenshot #live-rest
-> screens/step-forward-link-rest-<theme>.png
hover #shot-rest -> screenshot #live-rest
-> screens/step-forward-link-hover-<theme>.png
focus #shot-rest, press Tab, assert :focus-visible -> screenshot #live-rest
-> screens/step-forward-link-focus-<theme>.png
Six files and not eight: there is no disabled state to shoot, for the reason above.
The states are reached the way a person reaches them: a real pointer and a real Tab. Step 9
checks these files for a byte shift, because a snapshot nobody re-takes freezes silently, and
a frozen snapshot is worse than none: it looks checked.
Visited is not styled and will not be. Both targets are anchors on the page a person is already reading, so every one of them is visited within seconds of arriving, and a visited colour would turn the reveal grey behind a person as they walk down it. The one screen this component stands on is the screen whose whole job is that walking down it feels good.
The technical half
| Reads | Which token | Growing from |
|---|---|---|
| its label | --text-body | --slate |
| the arrow | --text-muted | --muted. One tier down from the label: the glyph is a hint about direction, not a word |
| the underline at rest | --line-container | --hairline. Decorative, so it carries no threshold, which is exactly why it may be this quiet |
| label, arrow and underline on hover | --text-action | --petrol |
| the focus ring | --color-focus | --petrol |
| the floor | --tap | primitive, straight: geometry has nothing to override |
One judgement in this file, named rather than buried. On hover the underline reads --text-action, an ink role, and not a line role. At rest it is quieter than the text above it, so it is decoration and takes --line-container; on hover the product paints it with exactly the same petrol as the label, so it stops being a separate line and travels with the ink. The only petrol line role in the system is --line-selected, whose declared job is the current selection, and a hovered link is not a selection. The value is identical either way, so the name is the whole difference. It is not a local call. Every link in this product underlines the same way, hairline at rest and the ink colour on hover, including the thirteen rules the census found doing one job, so the quiet inline link component meets this same question when it is built and should answer it the same way.
Lives in design/system/components/step-forward-link.css. Stands on 1 grey page, twice. Coloured screen carrying it: Guided Reveal. The registry's count of 2 is the number of links, not of pages.
<a class="next" href="#step-2">See what they are</a>
<a class="next" href="#step-3">See the monthly total</a>