Steps
The trace of a task as it runs, or a stepper across the top of a view: statuses, durations, stages that open while they work, and steps the reader can select.
c2-steps
A list of steps: the trace of a task as it runs, or a wizard's progress — vertical by default, or across the top
of a view with orientation="horizontal". Each row is a marker, a label,
an optional dimmed detail beside it and trailing text at the end — a duration, a count, a timestamp.
A step with sub-steps is a group, and a group is a disclosure: its own row is the summary and its sub-steps
are the detail. Every step is a row, and every row is visible — a group starts expanded and nothing ever
folds one away on its own. The chevron is there for the reader, collapsed in the markup starts a stage folded,
and the run only ever brings a folded stage back into view when it starts running or something in it fails.
Author it either way, and mix them freely:
- Declarative — slot
c2-stepchildren and nest them for sub-steps. - Data-driven — hand it a
stepsarray of{ id, label, detail, trailing, status, children }and it renders the tree itself. The array wins when both are present. During a run,updateStep(id, patch)changes one step without rebuilding the array.
Everything the markup fills with a slot, the data-driven mode fills with a renderer: renderMarker for the
marker slot, renderToggle for toggle, renderLabel / renderDetail / renderTrailing for the text, and
renderItem for that text all at once. Each is handed the node, its depth, its position, its dotted path and
the status actually in effect.
A parent step that does not author a status takes one from its children: the most urgent thing inside wins, so
a stage reports that it is running, or that something under it failed, without you setting it.
current is the shortcut a wizard wants: set it to the index of the active step and every top-level step that
has neither an explicit status nor sub-steps becomes success before it, current at it and pending after.
A step that arrives while the list is already on screen grows into place rather than appearing, and a marker
gives one beat as its status settles — which is what a trace being written in front of you should look like.
--c2-step--enter-duration: 0s turns the first off, and prefers-reduced-motion turns both off.
The connector rail between markers is drawn by CSS and off by default, because a trace does not want one — give
--c2-step__rail--width a width and a stepper gets its rail.
Numbering follows the tree: marker="number" draws a dotted path, so the first child of the third step reads
3.1 rather than a second 1.
Horizontal. orientation="horizontal" lays the top-level steps out side by side, each a column with its
marker on a connector rail and its text underneath. Sub-steps are not drawn there — a group still rolls their
status up, so a stage reads as running or failed — and the list scrolls sideways rather than squeezing a step
below --c2-step__row__horizontal--min-width.
Interactive. interactive turns every row that is not a disclosure into a button, and the list keeps one
selected step: selected names it by its value (a data node's id), or by its dotted path when it has none.
Pressing a step selects it and fires selection-change; a disabled step cannot be selected. That is how a view
follows a task — select the step that is running to show its log, select the result when it finishes, and the
reader can still press the first step to go back to the log. Selection is independent of status: the list
never moves it on its own.
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
marker | StepsMarker | icon | How each marker is drawn: a status glyph, the step's number, or nothing. |
orientation | StepsOrientation | vertical | Which way the list runs. A horizontal list draws its top-level steps side by side, and no sub-steps. |
interactive | boolean | false | Makes every row that is not a disclosure a button that selects its step. |
selected | string | — | The selected step of an interactive list: its value (a data node's id), or its dotted path when it has
none. Empty selects nothing. Set it to move the selection; the reader's presses set it too. |
current | number | -1 | Index of the active step, for a wizard. -1 (the default) leaves every status alone; otherwise a top-level
step with neither an explicit status nor sub-steps becomes success before this index, current at it and
pending after it. |
steps | StepNode[] | undefined | undefined | The steps as data, instead of c2-step children. Takes precedence over the slot. |
aria-label | string | null | null | Accessible name for the list. |
Slots
| Name | Description |
|---|---|
(default) | The c2-step children. Ignored when steps is set. |
Events
| Name | Type | Description |
|---|---|---|
selection-change | CustomEvent<StepsSelectionChangeEventDetail> | The reader selected another step of an interactive list. detail.value is the new selected, detail.path where the step sits. Not fired for programmatic changes. Does not bubble: several components fire selection-change, so a listener belongs on the element itself. |
step-toggle | CustomEvent<StepToggleEventDetail> | Fired by a c2-step when a group opens or closes, and bubbling to here. event.target is the step; detail.path says where it sits. |
CSS custom properties
Grouped by semantic target and state from --c2-<component>__<target>__<state>--<property>. Click a group to collapse it; click a name to copy it.
| Name | Type | Default | Description |
|---|---|---|---|
--c2-steps--gap | pixel | 0px | Space between rows. A stepper usually wants some; a trace does not. |
--c2-steps--background | color | — | |
--c2-steps--border | border | — | |
--c2-steps--border-radius | border-radius | — | |
--c2-steps--padding-block | padding | 0px | |
--c2-steps--padding-inline | padding | 0px | |
--c2-steps--overflow-y | overflow | visible | auto with a max-height keeps a long run in its own scroller. |
--c2-steps--max-height | max-height | none | |
--c2-steps__horizontal--overflow-x | overflow | auto | How a horizontal list that is wider than its box behaves. |
c2-step
One row of a Steps list: a marker, a label with optional dimmed detail, and trailing text at the end
of the row — a duration, a count, a timestamp.
A step with sub-steps is a group. Nest c2-step children and the row becomes the summary of a disclosure
and the children its detail, so a long run collapses to the shape of the task rather than to a wall of lines.
There is no second element to learn: the same tag is a leaf or a group depending on what is inside it.
Every step is a row, and every row is visible. A group starts expanded and nothing ever folds one away on
its own: the chevron is there for the reader, and collapsed in the markup starts a stage folded. The run only
ever brings a stage back into view — a folded one reopens when it starts running (running, current) or
when something in it goes wrong (error, warning).
A step is one row. Label, detail and trailing text sit on a single line and truncate with an ellipsis rather
than wrapping, so a hundred-step trace stays scannable, and a sub-step is indented far enough that its marker
lands under its parent's label. --c2-step__text--flex-direction: column still stacks the detail under the
label, which a wizard's descriptions want — that is two lines on purpose, and each of them is still one line.
In an interactive list a row is a button. Pressing it selects the step — the list keeps one selected step and
says so with selection-change — so a view can show what belongs to it: the log of the step that ran, the result
of the step that produced one. A group's row stays its disclosure; disabled keeps a step from being selected.
The step never sets its own depth, position, marker mode, orientation or selection: the parent c2-steps writes
them on every pass, so a step used on its own renders as a single root-level row.
States. The step never writes an attribute on its own host — not even the status the list rolls up or the
collapsed a reader toggles, neither of which reflects — so server-rendered markup matches the hydrated element.
Page CSS reads the step through custom states instead: its status (c2-step:state(running), :state(success),
:state(error), :state(warning), :state(current), :state(skipped), :state(pending)), :state(collapsed),
:state(has-children) for a group, :state(last) for the last step of its group, :state(grouped) when the list
holds any group, the marker mode as :state(marker-icon), :state(marker-number) or :state(marker-none), and
the two animations: :state(entering) while a step that arrived in a running trace grows into place, and
:state(settling) while its marker gives the beat of a changed status.
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
status | StepStatus | pending | State of the step. Drives the marker glyph and the accent colour, and reopens a folded group. Not reflected: the
parent list writes a rolled-up status onto a group that authored none, and a reflected one would be an attribute
the server never rendered. Read it as :state(<status>). |
label | string | — | Primary text, when the label slot is empty. |
detail | string | — | Dimmed secondary text beside the label, when the detail slot is empty. |
trailing | string | — | Text at the end of the row, when the trailing slot is empty. |
value | string | — | What the parent c2-steps calls this step in selected and selection-change. Without one, the step is
called by its dotted path — 2 for the second top-level step. |
disabled | boolean | false | In an interactive list, keeps the step from being selected. Its row stays visible, and its status still shows. |
collapsed | boolean | false | Whether this group is folded away. A group is expanded by default — every step in the list is a row you can
see — and only the reader, the markup or a reopening status ever changes that. Not reflected; read it as
:state(collapsed). |
Slots
| Name | Description |
|---|---|
(default) | Sub-steps. A step that has them is a group. |
label | Primary text. Falls back to the label attribute. |
detail | Secondary text beside the label. Falls back to the detail attribute. |
trailing | Content at the end of the row. Falls back to the trailing attribute. |
marker | Replaces the whole marker: a custom icon, an avatar, a number of your own. |
toggle | A disclosure affordance of your own — a chevron, a caret. Empty by default: a trace is a list of rows, and the row is already clickable. It is filled per step, so give a leaf an empty <span slot="toggle"></span> to keep its rows lined up with the groups above them. |
Events
| Name | Type | Description |
|---|---|---|
step-toggle | CustomEvent<StepToggleEventDetail> | A group was folded away or brought back. Bubbles. |
CSS parts
Shadow DOM styling hooks used with ::part(name). Prefer CSS custom properties when they cover the change.
| Name | Description |
|---|---|
frame | The box around the whole step, which is what grows when a new step arrives. |
row | The row itself: toggle, marker, text and trailing content. A group's row is its <summary>, and a selectable row in an interactive list is a <button>. |
toggle | Disclosure region containing the assigned toggle slot for a group. |
marker | Round marker region containing the marker slot or status fallback at the start of the row. |
rail | The connector between this marker and the next. |
label | Primary-text region containing the label slot or property fallback. |
detail | Secondary-text region containing the detail slot or property fallback. |
trailing | End-aligned text region containing the trailing slot or property fallback. |
children | The box holding the sub-steps. |
CSS custom properties
Grouped by semantic target and state from --c2-<component>__<target>__<state>--<property>. Click a group to collapse it; click a name to copy it.
| Name | Type | Default | Description |
|---|---|---|---|
--c2-step--transition-duration | time | 150ms | Colour transitions, and the beat a marker gives when its status settles. |
--c2-step--enter-duration | time | 260ms | How long a step arriving in a running trace takes to grow into place. 0s turns the animation off. |
--c2-step--enter-translate | translate | -4px | How far it slides while it does. |
--c2-step__row--gap | pixel | 10px | Space between the marker, the text and the trailing content. |
--c2-step__row--padding-block | padding | 7px | |
--c2-step__row--padding-inline | padding | 12px | |
--c2-step__row--indent | pixel | calc(var(--c2-step__marker--size, 16px) + var(--c2-step__row--gap, 10px)) | Extra inset per level of nesting. The default puts a sub-step's marker under its parent's label, which is what makes the nesting read without drawing anything. |
--c2-step__row--border-radius | border-radius | — | |
--c2-step__row--background | color | — | |
--c2-step__row--border-bottom | border | 1px solid #e4e4e7 | The hairline under each row. |
--c2-step__row--outline | border | 2px solid rgb(2, 101, 220) | Focus ring of a row that is a button: a group's, and every selectable one. |
--c2-step__row__hover--background | color | — | Set it to make the rows respond to the pointer. |
--c2-step__row__selected--background | color | — | Background of the selected row in an interactive list. None by default: the label colour alone marks the selection. |
--c2-step__row__selected--box-shadow | box-shadow | — | An indicator for the selected row — inset 0 -2px 0 currentColor underlines it. |
--c2-step__row__disabled--opacity | opacity | 0.38 | A disabled step in an interactive list. |
--c2-step__row__horizontal--border-bottom | border | none | The hairline under each step of a horizontal list, which has none by default. |
--c2-step__row__horizontal--min-width | pixel | 96px | The narrowest a step of a horizontal list gets before the list scrolls. |
--c2-step__row__horizontal--grid-template-areas | grid-template-areas | "marker rail rail" "text text text" "trailing trailing trailing" | Where the text sits around the marker in a horizontal list. The cells are marker, rail, text and trailing, over three columns sized auto auto 1fr; the rail always takes the last one and runs on to the next step. Text over the marker: "text text text" "trailing trailing trailing" "marker rail rail", with --c2-step__row__horizontal--align-content: end. Beside it: "marker text rail" ". trailing .". Before it: "text marker rail" "trailing . .". |
--c2-step__row__horizontal--align-content | align-content | start | Which end of a horizontal step its rows pack to when a neighbour is taller. end with the text over the marker keeps every marker on one line. |
--c2-step__row__horizontal--row-gap | pixel | 8px | Space between the marker's line and the text above or below it in a horizontal list. |
--c2-step__toggle--size | pixel | 14px | Width of the toggle slot's column, when it is filled. |
--c2-step__toggle--gap | pixel | 4px | Space between that column and the marker. |
--c2-step__toggle--color | color | #a1a1aa | |
--c2-step__guide--color | color | #e4e4e7 | A vertical rule at each ancestor's depth, for a file-tree look. Off by default. |
--c2-step__guide--width | pixel | 0px | Width of that rule. 1px turns the guides on. |
--c2-step__marker--size | pixel | 16px | |
--c2-step__marker--font-size | font-size | 10px | Size of the dotted path in marker="number". |
--c2-step__marker--padding-inline | padding | 3px | Breathing room either side of a dotted path, which is what turns the circle into a pill when the number is long. Only marker="number" uses it. |
--c2-step__marker--font-weight | font-weight | 600 | |
--c2-step__marker--border-radius | border-radius | 999px | |
--c2-step__marker--border | border | 1px solid #bcbcc6 | The ring, drawn for the states that have no glyph of their own (pending, current, running) and for every marker="number" step. A finished step is its glyph, so it has no ring. |
--c2-step__marker--background | color | transparent | |
--c2-step__marker--color | color | #71717a | |
--c2-step__rail--width | pixel | 0px | Width of the connector. 0px is the trace look; 2px gives a stepper its rail. |
--c2-step__rail--color | color | #e4e4e7 | |
--c2-step__rail--gap | pixel | 4px | Space between the marker and the rail. |
--c2-step__rail__horizontal--width | pixel | 2px | Thickness of the connector in a horizontal list, which always links one step to the next: it runs from this step's marker (or its label, when the label comes after the marker) through the gap to where the next step starts, one --c2-step__rail--gap short of it at each end. |
--c2-step__label--color | color | #18181b | |
--c2-step__label--font-size | font-size | 13px | |
--c2-step__label--font-weight | font-weight | 500 | |
--c2-step__label--font-family | font-family | ui-monospace, SFMono-Regular, Menlo, Consolas, monospace | |
--c2-step__label__selected--color | color | rgb(2, 101, 220) | Label colour of the selected row: what marks the selection by default. |
--c2-step__label__selected--font-weight | font-weight | — | Label weight of the selected row. Falls back to the label weight. |
--c2-step__detail--color | color | #71717a | |
--c2-step__detail--font-size | font-size | — | Falls back to the label size. |
--c2-step__detail--font-weight | font-weight | 400 | |
--c2-step__detail--gap | pixel | 8px | Space between the label and the detail when they share a line. |
--c2-step__detail--row-gap | pixel | 2px | Space between them when they are stacked. |
--c2-step__text--flex-direction | flex-direction-row | row | column puts the detail on its own line under the label, which a wizard's descriptions want. It is the only variable that switch needs: the gap and the alignment follow it. |
--c2-step__text--align-items | align-items | baseline | How the label and the detail line up across the row. Stacked, they are flush left whatever this says. |
--c2-step__trailing--color | color | #71717a | |
--c2-step__trailing--font-size | font-size | — | Falls back to the label size. |
--c2-step__trailing--font-weight | font-weight | 400 | |
--c2-step__success--color | color | #16a34a | Marker colour when the step succeeded. |
--c2-step__error--color | color | #dc2626 | |
--c2-step__warning--color | color | #d97706 | |
--c2-step__running--color | color | rgb(2, 101, 220) | |
--c2-step__current--color | color | rgb(2, 101, 220) | |
--c2-step__skipped--color | color | #71717a | Marker and label colour when the step was skipped. |