Monet
@etched/monet 0.0.1

Steps

Ported

Numbered steps in an ordered list: done (a check), current (aria-current="step") or upcoming, and any of them can have an error. The steps are labels; the app moves between them with its own buttons, and with onSelect back to a done step. A polite live region says "Step n of m" when current changes, as focus stays on the app's Next button.

Step 2 of 3

  1. Done: Candidate
  2. Your relationship
  3. Role
import { Steps, type StepsItem } from "@etched/monet/steps";

const referral: StepsItem[] = [{ label: "Candidate" }, { label: "Your relationship" }, { label: "Role" }, { label: "Review" }];

export function Preview() {
  return <Steps aria-label="Referral steps" steps={referral.slice(0, 3)} current={1} />;
}

Installation

import { Steps } from "@etched/monet/steps";

Examples

Wizard header

A row of steps above the form, with the count. Done steps are buttons that go back to them (onSelect); the app's own buttons move forward.

Step 2 of 4

  1. Your relationship
  2. Role
  3. Review
import { useState } from "react";
import { Button } from "@etched/monet/button";
import { Steps, type StepsItem } from "@etched/monet/steps";

const referral: StepsItem[] = [{ label: "Candidate" }, { label: "Your relationship" }, { label: "Role" }, { label: "Review" }];

export function WizardHeader() {
  const [step, setStep] = useState(1);

  return (
    <div className="grid" style={{ maxWidth: "none" }}>
      <Steps aria-label="Referral steps" steps={referral} current={step} showCount onSelect={setStep} />
      <div className="row">
        <Button disabled={step === 0} onClick={() => setStep(step - 1)}>
          Back
        </Button>
        <Button variant="primary" disabled={step === referral.length} onClick={() => setStep(step + 1)}>
          {step >= referral.length - 1 ? "Send referral" : "Next"}
        </Button>
      </div>
    </div>
  );
}

Setup checklist

A column of steps, each with a line of help, like Topo setup.

Step 2 of 3

  1. Done: Install Topo

    Add the extension from the Chrome Web Store.

  2. Confirm your account

    Sign in to LinkedIn in the same Chrome profile.

  3. Share connections

    Only the platform admin can see them. You can remove them at any time.

import { Steps, type StepsItem } from "@etched/monet/steps";

const topo: StepsItem[] = [
  { label: "Install Topo", description: "Add the extension from the Chrome Web Store." },
  { label: "Confirm your account", description: "Sign in to LinkedIn in the same Chrome profile." },
  { label: "Share connections", description: "Only the platform admin can see them. You can remove them at any time." },
];

export function SetupChecklist() {
  return <Steps aria-label="Topo setup" orientation="vertical" steps={topo} current={1} showCount />;
}

A step with an error

error marks a step that has a problem or is blocked, and its description says what is wrong. A done step with an error is still a button back to it. Without showCount, screen readers still hear "Step n of m" when the step changes.

Step 3 of 3

  1. ana-ruiz-cv.pdf

  2. Two candidates match this résumé. Go back and pick one.

  3. Confirm

    Check the details, then add the candidate to the pipeline.

import { useState } from "react";
import { Steps, type StepsItem } from "@etched/monet/steps";

const intake: StepsItem[] = [
  { label: "Upload résumé", description: "ana-ruiz-cv.pdf" },
  { label: "Match candidate", description: "Two candidates match this résumé. Go back and pick one.", error: true },
  { label: "Confirm", description: "Check the details, then add the candidate to the pipeline." },
];

export function AStepWithAnError() {
  const [step, setStep] = useState(2);

  return <Steps aria-label="Intake steps" orientation="vertical" steps={intake} current={step} onSelect={setStep} />;
}

Every step done

current at the number of steps marks them all done.

2 of 2 steps done

  1. Done: Select swarmers
  2. Done: Write messages
import { Steps, type StepsItem } from "@etched/monet/steps";

const swarm: StepsItem[] = [{ label: "Select swarmers" }, { label: "Write messages" }];

export function EveryStepDone() {
  return <Steps aria-label="Create swarm steps" steps={swarm} current={swarm.length} showCount />;
}

Accessibility

  • With onSelect, each done step is a native button: Tab reaches it, and Enter or Space goes back to it. Other steps take no focus.
  • An ordered list named by the required aria-label. The current step has aria-current="step".
  • The numbers and checks are hidden. Done steps say "Done:" and steps with error say "Error:" before the label.
  • A polite live region says "Step 2 of 4" when current changes, also without showCount.
  • Move between steps with your own Back and Next buttons. On a step with error, say what is wrong in description.

API reference

PropTypeDefaultDescription
steps *StepsItem[]–The steps, in order.
current *number–The index of the current step, from 0. Steps before it are done, steps after it are upcoming. steps.length marks every step done.
aria-label *string–Names the list, for example "Referral steps".
orientation"horizontal" | "vertical""horizontal"A row (a wizard header) or a column (a setup checklist).
showCountboolean–Shows "Step n of m" above the steps. Screen readers hear it when the step changes, shown or not.
onSelect(index: number) => void–Makes each done step a button that calls this with its index, to go back to it. The current and upcoming steps stay text.
countLabel(current: number, total: number) => string"Step 2 of 4", or "4 of 4 steps done"The count's text, from current and the number of steps. Pass translated text, as for the other labels.
doneLabelstring"Done"Screen readers hear it before a done step's label.
errorLabelstring"Error"Screen readers hear it before the label of a step with an error.