Steps show where a reader is in a short sequence, such as a checkout or a setup. They are an ordered list whose markers are numbered by a CSS counter. Only the current step is marked, with aria-current="step"; the stylesheet works out the rest.
The current step has aria-current="step". Its marker rises as a small sheet with an ink contour.
A step is complete when the current one comes after it: .steps-item:has(~ .steps-item[aria-current="step"]). Its marker is an ink disc with the check cut out of it, so the mark is the surface showing through and reads in both themes, and the line leading on from it turns to ink.
Every other step waits in a well, with its number.
Move the attribute to the next item and all three update. When the whole sequence is done, no step is current: add steps-complete to the list and every step is complete.
steps-vertical sets one step under another, for a narrow column or for steps that carry a sentence each. Wrap the words in steps-body and add a steps-description.
Vertical
<olclass="steps steps-vertical max-w(320)mx-auto"><liclass="steps-item"><spanclass="steps-body"><spanclass="steps-title">Create the project</span><spanclass="steps-description">Named atlas, in the Master workspace.</span></span></li><liclass="steps-item"aria-current="step"><spanclass="steps-body"><spanclass="steps-title">Connect a repository</span><spanclass="steps-description">Choose where the code lives.</span></span></li><liclass="steps-item"><spanclass="steps-body"><spanclass="steps-title">Deploy</span><spanclass="steps-description">The first build takes about a minute.</span></span></li></ol>
When a reader may go back, make the title of a complete step a link. Steps that wait stay plain text.
As links
<navaria-label="Checkout"><olclass="steps"><liclass="steps-item"><aclass="steps-title link link-quiet"href="#cart">Cart</a></li><liclass="steps-item"><aclass="steps-title link link-quiet"href="#shipping">Shipping</a></li><liclass="steps-item"aria-current="step"><spanclass="steps-title">Payment</span></li><liclass="steps-item"><spanclass="steps-title">Review</span></li></ol></nav>
Use an <ol>: the order is the meaning, and the list announces how many steps there are.
aria-current="step" tells assistive technology which step is current. The check and the ink are visual only, so the list order carries completeness for a screen reader.
Keep horizontal steps to four or five short titles. Past that, use steps-vertical.
/* * Steps: where a reader is in a short sequence. An ordered list whose markers are numbered by a CSS counter. * Only the current step is marked, with aria-current="step": the steps before it are complete and take ink * and a check, and the steps after it wait in a well. A line joins each marker to the next. When the whole * sequence is done, steps-complete on the list completes every step. */@layer components {.steps{--steps-marker:1.5rem;--steps-gap:.75rem;display:flex;gap:var(--steps-gap);min-width:0;margin:0;padding:0;color:var(--color-text-muted);font-size:.8125rem;line-height:1.4;list-style:none;counter-reset: steps;/* Every step is done: none is current, and the list rests in body ink. */&.steps-complete{color:var(--color-text-body);}/* One step under another, for a narrow column or steps that carry a sentence each. */&.steps-vertical{flex-direction:column;gap:0;}}.steps-item{position:relative;display:flex;flex:110;align-items:center;gap:.5rem;min-width:0;counter-increment: steps;/* The last step has nothing to lead to, so it takes only the room it needs. */&:last-child{flex:none;}/* The marker: a number in a well while the step waits. */&::before{content:counter(steps);display:grid;flex:none;place-items:center;width:var(--steps-marker);height:var(--steps-marker);border-radius:999px;background:var(--color-surface-inset);color:var(--color-text-muted);box-shadow:var(--shadow-pressed);font:var(--font-weight-control).71875rem/1var(--font-family-sans);font-variant-numeric:tabular-nums;}/* The line to the next step. */&:not(:last-child)::after{content:'';flex:111rem;min-width:1rem;height:1px;background:var(--color-line-divider);}/* Current: the marker rises as a small sheet with an ink contour, and the words take strong ink. */&[aria-current="step"]{color:var(--color-text-strong);&::before{@apply--surface-sheet;color:var(--color-text-strong);box-shadow:000.5pxvar(--color-text-strong),0001pxcolor-mix(in oklab,var(--color-text-strong)22%,transparent),01px2px-1pxvar(--color-shade-1),02px4px-2pxvar(--color-shade-2);}}/* * Complete: every step that has the current one after it, or every step once the list is marked * steps-complete. An ink disc with the check cut out of it, so the mark is the surface showing through * and reads on moonstone and obsidian alike. */&:is(:has(~.steps-item[aria-current="step"]),.steps-complete.steps-item){color:var(--color-text-body);&::before{content:'';background:var(--color-surface-inverse);box-shadow:none;mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 8.4 2.6 2.6 5.4-6'/%3E%3C/svg%3E")center/.875remno-repeat,linear-gradient(black,black);mask-composite:exclude;}&::after{background:var(--color-text-strong);}}@media(forced-colors:active){&::before{outline:1pxsolid CanvasText;outline-offset:-1px;}&[aria-current="step"]::before{outline:2pxsolid Highlight;}&::after{background: CanvasText;}}}/* The words of a step: a title and an optional line under it. */.steps-body{display:grid;gap:.0625rem;min-width:0;}.steps-title{font-weight:var(--font-weight-control);white-space:nowrap;}.steps-description{color:var(--color-text-muted);font-size:.75rem;font-weight:400;text-wrap: pretty;overflow-wrap: anywhere;}/* Vertical: the marker stands on a column and the line runs down from it to the next marker. */.steps-vertical.steps-item{flex:none;align-items:flex-start;gap:.75rem;padding-bottom:1.25rem;&:last-child{padding-bottom:0;}&:not(:last-child)::after{position:absolute;top:calc(var(--steps-marker)+.25rem);bottom:.25rem;left:calc(var(--steps-marker)/2);flex:none;width:1px;min-width:0;height:auto;}&:dir(rtl)::after{right:calc(var(--steps-marker)/2);left:auto;}}.steps-vertical.steps-body{padding-top:calc((var(--steps-marker)-1lh)/2);}.steps-vertical.steps-title{white-space:normal;}}