An ordered set of steps that collapse into editable summaries as they are completed, ending in one explicit commit.
SetupFlow for two to five dependent decisions on one surface that end in a single explicit commit — send, create, run, publish. Later steps depend on earlier ones, the whole configuration is reviewable before it is committed, and abandoning midway discards cleanly.Form with a FormLayout and one submit for a plain set of fields. Splitting one form into steps adds ceremony without adding structure.Panels when the settings are independent and each saves on its own.Stepper when the steps live on separate screens and all you need is a progress indicator. See the wizard layout pattern.AccordionGroup when the sections are optional and can be opened in any order.ConfirmationModal whose confirm is isCritical and labelled "Discard" and whose cancel is labelled "Keep editing". Close a pristine flow silently. SetupFlow does not own this.Drawer at size="page" when the flow needs a working surface beside it, such as a live preview. See the setup flow with a preview pattern.isFinal) — the review. It has no summary, never collapses, its header row is inert, and its footer holds the commit. Give every flow exactly one, as its last step.className or inline styles. The public CSS variables (upcomingMarkerBackgroundColor, upcomingMarkerBorderColor, activeMarkerColor, completeMarkerBackgroundColor, invalidatedSummaryColor, markerGap, stepGap, and the rest — see setup-flow.style-exports.ts) are the supported way to adjust a flow, and can be set on any ancestor.Panel card, so its border, radius and background follow Panel. Change those through Panel's own variables rather than the flow's. Steps stack with a gap and nothing joins one to the next — there is no connector or rail.Coin. The summary stacks under the title inside the header row, and the open step's body lines up with the title rather than the marker.text-base-disabled is 2.29:1 on a light surface, which fails WCAG AA, and axe rejects it.value: the step matching currentStep is active, a step the flow counts as complete is complete, that same step with isInvalidated is invalidated, and everything else is upcoming. There is no status prop.isNextDisabled until its required inputs are valid. Never let an invalid step advance — the review at the end restates decisions, it does not catch errors.isNextLoading for the commit, which is normally asynchronous.summary only once it holds real values. A step renders its summary whenever it is not the open one and is not final, so a summary set ahead of time shows on a step nobody has reached.isInvalidated from the feature — the flow cannot know what an edit breaks. Name what changed in invalidatedSummary, keep whatever still applies, and block the commit until the step is re-confirmed. Never invalidate for a change the later step tolerates.isInvalidated, or drop it from completedSteps, only when the change genuinely broke its values.onBeforeEdit to intercept a reopen; return false to leave the step closed.isDisabled for a step that is unavailable. It keeps its place in the numbering, the flow advances straight past it, and it renders no control to reopen it. The same walk decides where an unseeded flow starts, so a disabled first step is not where the flow opens. Naming it in currentStep/defaultCurrentStep still opens it.aria-disabled on a disabled step. It renders no widget for the state to sit on and listitem does not support the attribute; the state is published as data-disabled instead.summary. The dimmed marker is the only visual signal, and it is hidden from assistive technology.tabindex="-1". A flow that has only just rendered never takes focus, including one that finds its first step a tick after mount.aria-hidden and decorative. Never let a marker be the only place a status appears; the label, the summary, and the header row's own text carry it.nextLabel: "Create export", "Send for previews". Never "Finish" or "Done".nextLabel at its default on every other step. Do not number them ("Continue to step 3") — the markers already show position.SetupFlow wrapping SetupFlowStep children, each holding its own body content and a SetupFlowFooter. In Ember these are PlumaSetupFlow, PlumaSetupFlowStep, and PlumaSetupFlowFooter, and body content goes in the default block — there are no named blocks.SetupFlow renders an ol and SetupFlowStep an li by default; both are polymorphic through as and accept Box props.label and value are the required props on a step. value is a stable id, unique within the flow, exactly like a TabsTab's.currentStep unset for an uncontrolled flow. Pass currentStep with onStepChange to control it. Both are a step's value, so what onStepChange reports can be handed straight back.defaultCurrentStep on an uncontrolled flow. Without it the flow has to find its first step from the DOM once the steps exist, so it is open at no step for a tick. It is required when the steps render asynchronously: the flow looks for its first step once as it mounts, so steps that arrive after a fetch resolves are never found and the flow stays open at none.completedSteps off and the flow keeps its own tally — moving off a step records it, and reopening an earlier step adds to the tally rather than winding it back. Pass it, as an array of step values, when the feature knows better: a resumed flow, or a step whose answers are gone. While it is set it is the whole answer.SetupFlowFooter with no onNext advances to the next step. The final step has nothing to advance to, so its footer must be given an onNext that commits.<button> spanning the step's columns, one tab stop, activated by both Enter and Space. The Edit affordance inside it is a Link rendered with as="span" and is not interactive — never make it a button or a link.editLabel defaults to Edit, or to Review while the step is invalidated. The header row's accessible name is composed from the row's own elements — the affordance, the step's label, and the summary it is showing ("Edit Name the export Weekly orders · CSV"). onBeforeEdit vetoes the whole row, not just the affordance.isFinal or isDisabled.invalidatedSummary replaces summary only while isInvalidated is set; a step with isInvalidated and no invalidatedSummary falls back to its summary.Divider above it is rendered by the footer; never add one.