OperantAI Workflow Design System
ComfortableCompact

Stepper shows sequential workflow progress as a vertical list of steps connected by a rail. Per the AGENTS.md decision table, use it for "sequential workflow progress" — not Tabs, which is for switching between views in one context rather than showing progression through steps.

Installation

HTML — already included if you loaded the 3 stylesheets — see the Installation guide.

React

npm install @operant/ui

@operant/ui is currently a monorepo workspace package, not yet published to the public npm registry.

Usage

HTML

<div class="ods-stepper" aria-label="Execution steps">
  <div class="ods-step is-done">
    <div class="ods-step-rail">
      <div class="ods-step-node" aria-hidden="true"><!-- check icon --></div>
      <div class="ods-step-line"></div>
    </div>
    <div class="ods-step-body">
      <div class="ods-step-title">Detect</div>
      <div class="ods-step-meta">alert ingested · 0.4s</div>
    </div>
  </div>
  <div class="ods-step is-active" aria-current="step">
    <div class="ods-step-rail">
      <div class="ods-step-node" aria-hidden="true">2</div>
    </div>
    <div class="ods-step-body">
      <div class="ods-step-title">Diagnose</div>
      <div class="ods-step-meta">4 tools · running…</div>
    </div>
  </div>
</div>

React

import { Stepper, Step } from "@operant/ui";

export default function Example() {
  return (
    <Stepper aria-label="Execution steps">
      <Step status="done" title="Detect">alert ingested · 0.4s</Step>
      <Step status="active" title="Diagnose">4 tools · running…</Step>
    </Stepper>
  );
}

Examples

Full run flow (all four states)

One step in each of the four states: done (check icon, success color), active (numbered node, agent-active/iris color, aria-current="step"), error (X icon, danger color), and pending (plain numbered node — there is no dedicated "pending" modifier class; the unstyled .ods-step node IS the pending look). Note the last step in the list has no trailing .ods-step-line — Stepper omits the connector after the final child automatically.

HTML

<div class="ods-stepper" aria-label="Execution steps">
  <div class="ods-step is-done">
    <div class="ods-step-rail">
      <div class="ods-step-node" aria-hidden="true"><svg width="12" height="12"><!-- check --></svg></div>
      <div class="ods-step-line"></div>
    </div>
    <div class="ods-step-body">
      <div class="ods-step-title">Detect</div>
      <div class="ods-step-meta">alert ingested · 0.4s</div>
    </div>
  </div>
  <div class="ods-step is-active" aria-current="step">
    <div class="ods-step-rail">
      <div class="ods-step-node" aria-hidden="true">2</div>
      <div class="ods-step-line"></div>
    </div>
    <div class="ods-step-body">
      <div class="ods-step-title">Diagnose</div>
      <div class="ods-step-meta">4 tools · running…</div>
    </div>
  </div>
  <div class="ods-step is-error">
    <div class="ods-step-rail">
      <div class="ods-step-node" aria-hidden="true"><svg width="12" height="12"><!-- x --></svg></div>
      <div class="ods-step-line"></div>
    </div>
    <div class="ods-step-body">
      <div class="ods-step-title">Remediate</div>
      <div class="ods-step-meta">tool call `deploy` failed</div>
    </div>
  </div>
  <div class="ods-step">
    <div class="ods-step-rail">
      <div class="ods-step-node" aria-hidden="true">4</div>
    </div>
    <div class="ods-step-body">
      <div class="ods-step-title">Verify</div>
    </div>
  </div>
</div>

React

<Stepper aria-label="Execution steps">
  <Step status="done" title="Detect">alert ingested · 0.4s</Step>
  <Step status="active" title="Diagnose">4 tools · running…</Step>
  <Step status="error" title="Remediate">tool call `deploy` failed</Step>
  <Step status="pending" title="Verify" />
</Stepper>

Stepper automatically computes each Step's 1-based index (shown inside the node for pending/active steps) and marks the last child isLast so its rail omits the trailing line — you never pass index/isLast yourself unless rendering a Step outside a Stepper.

API Reference

<Stepper>

PropTypeDefaultDescription
childrenReactNode—Required. Step elements; index/isLast are injected automatically based on list position.
classNamestring—Merged with .ods-stepper.

<Step>

PropTypeDefaultDescription
status"pending" | "active" | "done" | "error"—Required. Drives the node's modifier class, icon, and color.
titlestring—Required. Rendered in .ods-step-title.
childrenReactNode—Supporting detail rendered under the title in .ods-step-body (timing, tool count, error reason).
indexnumber—1-based position shown inside the node for pending/active steps. Set automatically by Stepper; only pass explicitly outside one.
isLastboolean—Omits the trailing rail connector. Set automatically by Stepper.
classNamestring—Merged with .ods-step.

status → modifier mapping: done → .is-done (success color, check icon), active → .is-active (agent-active/iris color, numbered node, aria-current="step"), error → .is-error (danger color, X icon), pending → no modifier class (plain numbered node).

HTML-side classes: .ods-stepper (vertical flex container), .ods-step (a single row) + state modifiers .is-done / .is-active / .is-error (no class for pending — that's the base look), .ods-step-rail (icon column), .ods-step-node (22px circle), .ods-step-line (connector to the next step), .ods-step-body, .ods-step-title, .ods-step-meta (secondary detail text). Note: css/components.css also defines an .is-review modifier (human-review/amber color) not currently exposed as a StepStatus value in the React component — use it directly in HTML if you need a "needs human review" step state.

Accessibility

  • The active step carries aria-current="step", giving assistive tech an explicit marker for "where the user/agent currently is" beyond the visual color change.
  • Status icons (check/X) are aria-hidden="true" — the step's status is conveyed through the adjacent text title/meta, not decoded from the icon alone (AGENTS.md #4/#8: color/icon is never the only signal).
  • Give the Stepper container an aria-label (or aria-labelledby) describing what the sequence represents, e.g. "Execution steps".
  • The rail/line connectors are aria-hidden by omission (plain <div>s, not announced) — only the title and meta text carry semantic content.