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
Pick
Choose a component from the gallery.
Install
One command copies the source.
Ship
Edit it like your own code.
Three short steps, stacked in a narrow container.
Four steps
Audit the tokens
List every colour, size and radius the product already uses.
Build the primitives
Button, input and surface first, each with all its states.
Compose the screens
Assemble pages from primitives only. No one-off styles.
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
| Prop | Type | Default | Description |
|---|---|---|---|
| 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