Skip to content

feat(core): let a flow drop the Stepper's collapsed controls and label - #5935

Open
ernestt wants to merge 3 commits into
core-stepper-narrow-collapsefrom
core-stepper-collapsed-summary-props
Open

feat(core): let a flow drop the Stepper's collapsed controls and label#5935
ernestt wants to merge 3 commits into
core-stepper-narrow-collapsefrom
core-stepper-collapsed-summary-props

Conversation

@ernestt

@ernestt ernestt commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Note

Stacked on #5659. Base is core-stepper-narrow-collapse, so this diff is just the two new props. Review #5659 first; this can retarget to main once that lands.

Why

Feedback on #5659: the collapsed summary row often repeats the page around it on a phone. A wizard that brings its own Back/Continue gets two pairs of controls, and one that heads its own step gets the step named twice. The collapse is right to exist — it just had no way to yield the half a given flow already covers.

What

Two independent booleans on Stepper, both @default true:

prop off means
hasCollapsedControls no Previous/Next beneath a collapsed track
hasCollapsedLabel a collapsed track does not name the current step

Defaults preserve today's behavior exactly. Dropping one leaves the other in the row; dropping both removes the row entirely rather than leaving an empty one to spend the frame's gap.

Step needed no change — it already gates its summary portal on the slot existing, so withholding the slot is the entire implementation of hasCollapsedLabel.

Accessibility

Both props are purely visual. The summary row is aria-hidden and the <ol> keeps every step's name at any width, so neither prop can shorten what a screen reader hears. Asserted directly by a test that reads back the full sequence with both halves off.

The one sharp edge

Turning the controls off in the separated layout leaves a collapsed stepper with nothing to press — that layout gives up its step click targets along with its labels. That is the intent when the flow has its own controls, but it does mean the stepper is purely a progress indicator until it is wide again. on-track keeps its indicators pressable as nodes on the rail, so there it costs nothing. Named in the hasCollapsedControls JSDoc and the prop table rather than left to be discovered.

How the names were chosen

Arbitrated with a vibe test rather than picked; harness and full findings in internal/vibe-tests/stepper-collapse-naming-test. Four naming arms × four prompts with isolated agents, arm docs machine-generated from one template so no arm got a lexical head start, plus a five-sample recall probe that described the behavior with the props deliberately unnamed.

  • The generation battery tied 16/16 correct. Naming does not change whether an agent can use the API, so everything below is tie-break.
  • Shape: 5/5 recall samples produced two independent booleans and nobody produced an enum. The four states are genuinely independent, and an enum forces you to restate the half you did not want to change.
  • hasLabel is out: it collides with the existing label prop, which names the whole sequence. A real collision in the shipping API, not an artifact of the test.
  • Summary is out: it only ever won by leaking an internal name off a data attribute, and both Summary choosers started at Collapsed and switched for that reason. The judge also found that B and C results stopped to verify the prop would not kill the desktop labels 4/4, while A and D never did 0/4 — the Collapsed qualifier removes real scope doubt.
  • has over show: the probe preferred show* 5/5, but packages/core/src has ~30 has* booleans and zero show*. The 16/16 tie is the evidence that taking the house convention costs nothing for a prop that is always documented.

Docs

Updated in all three Stepper.doc.mjs variants (full, --dense, --zh): both props in the prop tables, a new Collapsed summary anatomy row so astryx-stepper-summary is no longer a theme target for a part the docs never mention, and a corrected best practice — the old "use the vertical orientation for narrow containers" now contradicted the component, and would still send an agent to orientation="vertical" on mobile.

Also picked up pre-existing drift while in the same tables: indicatorPosition was missing from both the dense and Chinese prop tables.

Test plan

Six new tests in Stepper.test.tsx:

  • drops the controls but keeps the name on request
  • drops the name but keeps the controls on request
  • leaves the bare track when neither half is wanted
  • keeps the sequence whole for a screen reader with both halves off
  • leaves a stepper that has room for its labels alone
  • leaves on-track nodes pressable after the controls are dropped

Local verification:

  • Stepper.test.tsx + docs-drift — 65 passed
  • docPropReferences + docPropLiterals — 22 passed, confirming the documented props match the real signature
  • astryx component Stepper rendered in all three variants; eslint and prettier clean

Known gap, pre-existing and left alone: docsZh has no anatomy block at all, so the new anatomy row does not reach Chinese readers. Closing that means translating all five entries and felt like a separate change.

Feedback on the narrow-container collapse: on a phone the summary row often
repeats the page around it. A wizard that brings its own Back/Continue gets
two pairs of controls, and one that heads its own step gets the step named
twice. The collapse is right to exist; it just had no way to yield the half
a given flow already covers.

Stepper now takes `hasCollapsedControls` and `hasCollapsedLabel`, both
defaulting to true, so a stepper that sets neither behaves exactly as it did
before. Dropping one leaves the other in the row; dropping both removes the
row rather than leaving an empty one to spend the frame's gap. `Step` needed
no change — it already gates its summary portal on the slot existing, so
withholding the slot is the whole implementation of `hasCollapsedLabel`.

The names were arbitrated rather than picked. Four naming arms times four
prompts, isolated agents, arm docs machine-generated from one template so no
arm got a lexical head start, plus a five-sample recall probe that described
the behavior with the props deliberately unnamed. On the generation battery
the arms tied at 16/16 correct, so the naming does not change whether an
agent can use the API. The probe is what separated them: 5/5 reached for two
independent booleans and nobody produced an enum, which settles the shape; a
bare `hasLabel` collides with the existing `label` prop that names the whole
sequence; and `Summary` only ever won by leaking an internal name off a data
attribute, while `Collapsed` was the unprompted first instinct. The probe
preferred `show*` 5/5, but packages/core/src has ~30 `has*` booleans and zero
`show*`, and the 16/16 tie is the evidence that taking the house convention
costs nothing for a prop that is always documented. Harness and full findings
in internal/vibe-tests/stepper-collapse-naming-test.

Both props are purely visual. The row is aria-hidden and the <ol> keeps every
step's name at any width, so neither can shorten what a screen reader hears.

One sharp edge is documented rather than designed away, and it was the test's
largest finding: no agent in any arm could tell whether a collapsed track is
still tappable. In the `separated` layout the step targets go with the
labels, so turning the controls off leaves nothing to press until the stepper
is wide again. That is the intent when the flow has its own controls, but the
JSDoc now names the consequence and the `on-track` exception rather than
leaving it to be discovered.

Co-authored-by: Cursor <cursoragent@cursor.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Sep 2, 2026
@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Sep 4, 2026 1:25am UTC

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant