Skip to content
goodtabs UI
BrowseBrowse components

Process steps

Numbered steps on a hairline, stacked when narrow and in a row when wide.

npx shadcn@latest add https://ui.goodtabs.co/r/process-steps.json

With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/process-steps

  1. Pick

    Choose a component from the gallery.

  2. Install

    One command copies the source.

  3. Ship

    Edit it like your own code.

Three short steps, stacked in a narrow container.

Four steps

  1. Audit the tokens

    List every colour, size and radius the product already uses.

  2. Build the primitives

    Button, input and surface first, each with all its states.

  3. Compose the screens

    Assemble pages from primitives only. No one-off styles.

  4. Review in both themes

    Check contrast, focus and reduced motion before release.

Four steps with longer copy, in a row from 28rem up.

Usage

import { ProcessSteps } from "@/components/process-steps"

<ProcessSteps steps={[
  { title: "Pick", description: "Choose a component." },
  { title: "Install", description: "One command." },
]} />

Props

PropTypeDefaultDescription
steps{ title: string; description?: string }[]The steps in order. Numbers are generated from position.

Source

process-steps/process-steps.tsx
import * as React from "react";
import { cn } from "@/lib/utils";

type Step = {
  /** The step name, one short line. */
  title: string;
  /** One or two sentences. */
  description?: string;
};

type ProcessStepsProps = Omit<React.ComponentProps<"ol">, "children"> & {
  steps: Step[];
};

/**
 * Numbered steps on a hairline. Stacked in a narrow container, side by side from 28rem up.
 * A real ordered list, so a screen reader announces the position of every step.
 */
export function ProcessSteps({ steps, className, ...props }: ProcessStepsProps) {
  return (
    <div className="@container w-full">
      <ol
        data-slot="process-steps"
        className={cn("m-0 grid list-none gap-6 p-0 @md:auto-cols-fr @md:grid-flow-col @md:gap-8", className)}
        {...props}
      >
        {steps.map((step, index) => (
          <li key={step.title} className="flex flex-col gap-2 border-t pt-4">
            <span aria-hidden="true" className="font-mono text-xs leading-4 text-muted-foreground tabular-nums">
              {String(index + 1).padStart(2, "0")}
            </span>
            <h3 className="m-0 text-sm leading-5 font-medium text-foreground">{step.title}</h3>
            {step.description && <p className="m-0 text-sm text-muted-foreground">{step.description}</p>}
          </li>
        ))}
      </ol>
    </div>
  );
}

For AI agents

Hand your agent this page, the markdown at /components/process-steps/prompt.md, or the Copy AI prompt button above.

When to use

  • A how it works section with three to five steps
  • A short onboarding or release sequence
  • Any ordered list that should read as a path

When not to use

  • Interactive progress, which wants the stepper
  • Long instructions, which want prose
  • Unordered features, which want the feature grid

Agent notes

  • Server component; renders a real ol, numbers are aria-hidden because the list already announces position
  • Layout follows container width with @container, not the viewport, so it works in a card or a column
  • Keep titles to a few words and descriptions to two lines so the row stays level