- AccordionNew
- Alert
- Alert DialogNew
- AutocompleteNew
- AvatarNew
- Badge
- Breadcrumb
- Button
- CalendarNew
- CardNew
- Checkbox
- Checkbox GroupNew
- CollapsibleNew
- ComboboxNew
- CommandNew
- Context MenuNew
- Date PickerNew
- DialogNew
- DrawerNew
- EmptyNew
- FieldNew
- FieldsetNew
- FormNew
- FrameNew
- GroupNew
- Input
- Input GroupNew
- KbdNew
- Label
- MenuNew
- MeterNew
- Number Field
- OTP FieldNew
- Pagination
- PopoverNew
- Preview CardNew
- Progress
- Radio Group
- Scroll AreaNew
- Select
- Separator
- SheetNew
- SkeletonNew
- Slider
- Spinner
- StepperNew
- Switch
- Table
- TabsNew
- Textarea
- ToastNew
- ToggleNew
- Toggle GroupNew
- ToolbarNew
- Tooltip
Stepper
A sequence of steps that shows where the user is in a multi-stage flow.
import {
Stepper,
StepperIndicator,
StepperItem,
StepperSeparator,
StepperTrigger,
} from "mako-ui/components/stepper";
const steps = [1, 2, 3, 4];
export default function Particle() {
return (
<div className="mx-auto w-full max-w-xl">
<Stepper defaultValue={2}>
{steps.map((step) => (
<StepperItem className="not-last:flex-1" key={step} step={step}>
<StepperTrigger>
<StepperIndicator />
</StepperTrigger>
{step < steps.length && <StepperSeparator />}
</StepperItem>
))}
</Stepper>
</div>
);
}
Usage
import {
Stepper,
StepperDescription,
StepperIndicator,
StepperItem,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from "mako-ui/components/stepper"<Stepper defaultValue={2}>
<StepperItem className="not-last:flex-1" step={1}>
<StepperTrigger>
<StepperIndicator />
<StepperTitle>Account</StepperTitle>
</StepperTrigger>
<StepperSeparator />
</StepperItem>
<StepperItem className="not-last:flex-1" step={2}>
<StepperTrigger>
<StepperIndicator />
<StepperTitle>Delivery</StepperTitle>
</StepperTrigger>
<StepperSeparator />
</StepperItem>
<StepperItem step={3}>
<StepperTrigger>
<StepperIndicator />
<StepperTitle>Payment</StepperTitle>
</StepperTrigger>
</StepperItem>
</Stepper>Steps are numbered from 1. Every step below the active one is completed, the active one is highlighted, and the ones above it are inactive. Clicking a StepperTrigger makes its step the active one; leave the trigger out and drive value from your own controls for a read-only stepper.
API Reference
This is a custom component.
Stepper
Root ordered list. Holds the active step and the orientation and exposes both to its items through data-orientation.
| Prop | Type | Default | Description |
|---|---|---|---|
defaultValue | number | 1 | The step active on first render, when uncontrolled |
value | number | - | The active step, when controlled |
onValueChange | (step: number) => void | - | Called with the step whose trigger was activated |
orientation | "horizontal" | "vertical" | "horizontal" | Lays the steps out in a row or in a column |
StepperItem
List item for one step. Derives its state from step and the active step and exposes it as data-state (completed, active or inactive), plus data-orientation, data-disabled and data-loading.
| Prop | Type | Default | Description |
|---|---|---|---|
step | number | - | The 1-based position of the step. Required |
completed | boolean | false | Marks the step completed whatever the active step is |
disabled | boolean | false | Disables the step's trigger |
loading | boolean | false | Shows a spinner in the indicator while this step is active |
StepperTrigger
Button that makes its step the active one. Carries aria-current="step" while active, is disabled with its item, and supports the render prop for links.
StepperIndicator
The step marker. Shows the step number by default, swaps it for a checkmark once the step is completed and for a spinner while it is loading. Pass children to replace that content: a string or number keeps the number styling without the checkmark, anything else, such as an icon, an avatar or a visually hidden label under a progress bar, renders as is.
StepperTitle
Label of a step. Place it inside the trigger so it is part of the step's accessible name.
StepperDescription
Secondary text of a step, rendered muted.
StepperSeparator
Connector between two steps. Follows the root orientation and turns to the primary colour once the step it belongs to is completed. Hidden from assistive technology.
Examples
Numbers only
import {
Stepper,
StepperIndicator,
StepperItem,
StepperSeparator,
StepperTrigger,
} from "mako-ui/components/stepper";
const steps = [1, 2, 3, 4];
export default function Particle() {
return (
<div className="mx-auto w-full max-w-xl">
<Stepper defaultValue={2}>
{steps.map((step) => (
<StepperItem className="not-last:flex-1" key={step} step={step}>
<StepperTrigger>
<StepperIndicator>{step}</StepperIndicator>
</StepperTrigger>
{step < steps.length && <StepperSeparator />}
</StepperItem>
))}
</Stepper>
</div>
);
}
Tiny indicators
import {
Stepper,
StepperIndicator,
StepperItem,
StepperSeparator,
StepperTrigger,
} from "mako-ui/components/stepper";
const steps = [1, 2, 3, 4];
export default function Particle() {
return (
<div className="mx-auto w-full max-w-xl">
<Stepper defaultValue={2}>
{steps.map((step) => (
<StepperItem className="not-last:flex-1" key={step} step={step}>
<StepperTrigger>
<StepperIndicator className="size-4 data-[state=active]:border-2 data-[state=active]:border-primary data-[state=active]:bg-transparent *:data-[slot=stepper-indicator-number]:sr-only *:data-[slot=stepper-indicator-check]:size-3" />
</StepperTrigger>
{step < steps.length && <StepperSeparator />}
</StepperItem>
))}
</Stepper>
</div>
);
}
Controlled
- 1
- 2
- 3
- 4
"use client";
import { Button } from "mako-ui/components/button";
import {
Stepper,
StepperIndicator,
StepperItem,
StepperSeparator,
} from "mako-ui/components/stepper";
import { useState } from "react";
const steps = [1, 2, 3, 4];
export default function Particle() {
const [currentStep, setCurrentStep] = useState(2);
return (
<div className="mx-auto flex w-full max-w-xl flex-col items-center gap-8">
<Stepper onValueChange={setCurrentStep} value={currentStep}>
{steps.map((step) => (
<StepperItem className="not-last:flex-1" key={step} step={step}>
<StepperIndicator />
{step < steps.length && <StepperSeparator />}
</StepperItem>
))}
</Stepper>
<div className="flex gap-4">
<Button
className="w-32"
disabled={currentStep === 1}
onClick={() => setCurrentStep((step) => step - 1)}
variant="outline"
>
Previous
</Button>
<Button
className="w-32"
disabled={currentStep > steps.length}
onClick={() => setCurrentStep((step) => step + 1)}
variant="outline"
>
Next
</Button>
</div>
</div>
);
}
Loading step
- 1
- 2
- 3
- 4
"use client";
import { Button } from "mako-ui/components/button";
import {
Stepper,
StepperIndicator,
StepperItem,
StepperSeparator,
} from "mako-ui/components/stepper";
import { useState } from "react";
const steps = [1, 2, 3, 4];
export default function Particle() {
const [currentStep, setCurrentStep] = useState(2);
const [loading, setLoading] = useState(false);
const goToNextStep = () => {
setLoading(true);
setTimeout(() => {
setCurrentStep((step) => step + 1);
setLoading(false);
}, 1000);
};
return (
<div className="mx-auto flex w-full max-w-xl flex-col items-center gap-8">
<Stepper onValueChange={setCurrentStep} value={currentStep}>
{steps.map((step) => (
<StepperItem
className="not-last:flex-1"
key={step}
loading={loading}
step={step}
>
<StepperIndicator />
{step < steps.length && <StepperSeparator />}
</StepperItem>
))}
</Stepper>
<div className="flex gap-4">
<Button
className="w-32"
disabled={currentStep === 1 || loading}
onClick={() => setCurrentStep((step) => step - 1)}
variant="outline"
>
Previous
</Button>
<Button
className="w-32"
disabled={currentStep > steps.length}
loading={loading}
onClick={goToNextStep}
variant="outline"
>
Next
</Button>
</div>
</div>
);
}
Mixed indicators
import {
Stepper,
StepperIndicator,
StepperItem,
StepperSeparator,
StepperTrigger,
} from "mako-ui/components/stepper";
import { Truck } from "mako-ui/icons/truck";
export default function Particle() {
return (
<div className="mx-auto w-full max-w-xl">
<Stepper defaultValue={2}>
<StepperItem className="not-last:flex-1" step={1}>
<StepperTrigger>
<StepperIndicator className="size-8">
<img
alt="Luke Tracy"
className="size-8 rounded-full object-cover"
height={32}
src="/images/portrait-1.jpg"
width={32}
/>
</StepperIndicator>
</StepperTrigger>
<StepperSeparator />
</StepperItem>
<StepperItem className="not-last:flex-1" loading step={2}>
<StepperTrigger>
<StepperIndicator />
</StepperTrigger>
<StepperSeparator />
</StepperItem>
<StepperItem className="not-last:flex-1" step={3}>
<StepperTrigger>
<StepperIndicator>
<Truck aria-hidden="true" />
<span className="sr-only">Delivery</span>
</StepperIndicator>
</StepperTrigger>
</StepperItem>
</Stepper>
</div>
);
}
Labels
import {
Stepper,
StepperIndicator,
StepperItem,
StepperTitle,
StepperTrigger,
} from "mako-ui/components/stepper";
const steps = [
{ step: 1, title: "Account" },
{ step: 2, title: "Delivery" },
{ step: 3, title: "Payment" },
{ step: 4, title: "Review" },
];
export default function Particle() {
return (
<div className="mx-auto w-full max-w-xl">
<Stepper className="items-start gap-4" defaultValue={2}>
{steps.map(({ step, title }) => (
<StepperItem className="flex-1" key={step} step={step}>
<StepperTrigger className="w-full flex-col items-start gap-2 rounded">
<StepperIndicator className="h-1 w-full">
<span className="sr-only">{step}</span>
</StepperIndicator>
<StepperTitle>{title}</StepperTitle>
</StepperTrigger>
</StepperItem>
))}
</Stepper>
</div>
);
}
Paginated
- 1
- 2
- 3
- 4
"use client";
import { Button } from "mako-ui/components/button";
import {
Stepper,
StepperIndicator,
StepperItem,
} from "mako-ui/components/stepper";
import { ArrowChevronLeft } from "mako-ui/icons/arrow-chevron-left";
import { ArrowChevronRight } from "mako-ui/icons/arrow-chevron-right";
import { useState } from "react";
const steps = [1, 2, 3, 4];
export default function Particle() {
const [currentStep, setCurrentStep] = useState(2);
return (
<div className="mx-auto flex w-full max-w-xl items-center gap-2">
<Button
aria-label="Previous step"
className="shrink-0"
disabled={currentStep === 1}
onClick={() => setCurrentStep((step) => step - 1)}
size="icon"
variant="ghost"
>
<ArrowChevronLeft aria-hidden="true" />
</Button>
<Stepper
className="gap-1"
onValueChange={setCurrentStep}
value={currentStep}
>
{steps.map((step) => (
<StepperItem className="flex-1" key={step} step={step}>
<StepperIndicator className="h-1 w-full">
<span className="sr-only">{step}</span>
</StepperIndicator>
</StepperItem>
))}
</Stepper>
<Button
aria-label="Next step"
className="shrink-0"
disabled={currentStep === steps.length}
onClick={() => setCurrentStep((step) => step + 1)}
size="icon"
variant="ghost"
>
<ArrowChevronRight aria-hidden="true" />
</Button>
</div>
);
}
Progress
- 1
- 2
- 3
- 4
Step 1 of 4
"use client";
import { Button } from "mako-ui/components/button";
import {
Stepper,
StepperIndicator,
StepperItem,
} from "mako-ui/components/stepper";
import { useState } from "react";
const steps = [1, 2, 3, 4];
export default function Particle() {
const [currentStep, setCurrentStep] = useState(1);
return (
<div className="mx-auto flex w-full max-w-xl flex-col items-center gap-8">
<div className="flex w-full flex-col gap-3">
<Stepper onValueChange={setCurrentStep} value={currentStep}>
{steps.map((step) => (
<StepperItem className="flex-1" key={step} step={step}>
<StepperIndicator className="h-2 w-full rounded-none">
<span className="sr-only">{step}</span>
</StepperIndicator>
</StepperItem>
))}
</Stepper>
<p
aria-live="polite"
className="font-medium text-muted-foreground text-sm tabular-nums"
>
Step {currentStep} of {steps.length}
</p>
</div>
<div className="flex gap-4">
<Button
className="w-32"
disabled={currentStep === 1}
onClick={() => setCurrentStep((step) => step - 1)}
variant="outline"
>
Previous
</Button>
<Button
className="w-32"
disabled={currentStep >= steps.length}
onClick={() => setCurrentStep((step) => step + 1)}
variant="outline"
>
Next
</Button>
</div>
</div>
);
}
Titles and descriptions
import {
Stepper,
StepperDescription,
StepperIndicator,
StepperItem,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from "mako-ui/components/stepper";
const steps = [
{ description: "Sign in or register", step: 1, title: "Account" },
{ description: "Address and shipping", step: 2, title: "Delivery" },
{ description: "Card or invoice", step: 3, title: "Payment" },
];
export default function Particle() {
return (
<div className="mx-auto w-full max-w-xl">
<Stepper defaultValue={2}>
{steps.map(({ step, title, description }) => (
<StepperItem
className="relative flex-1 flex-col"
key={step}
step={step}
>
<StepperTrigger className="flex-col gap-3 rounded">
<StepperIndicator />
<div className="flex flex-col gap-0.5 px-2 text-center">
<StepperTitle>{title}</StepperTitle>
<StepperDescription className="max-sm:hidden">
{description}
</StepperDescription>
</div>
</StepperTrigger>
{step < steps.length && (
<StepperSeparator className="absolute inset-s-[calc(50%+0.875rem)] top-3 m-0 w-[calc(100%-1.75rem)] flex-none -translate-y-1/2" />
)}
</StepperItem>
))}
</Stepper>
</div>
);
}
Inline titles
import {
Stepper,
StepperIndicator,
StepperItem,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from "mako-ui/components/stepper";
const steps = [
{ step: 1, title: "Account" },
{ step: 2, title: "Delivery" },
{ step: 3, title: "Payment" },
];
export default function Particle() {
return (
<div className="mx-auto w-full max-w-xl">
<Stepper defaultValue={2}>
{steps.map(({ step, title }) => (
<StepperItem
className="not-last:flex-1 max-md:items-start"
key={step}
step={step}
>
<StepperTrigger className="rounded max-md:flex-col">
<StepperIndicator />
<StepperTitle className="text-center md:text-start">
{title}
</StepperTitle>
</StepperTrigger>
{step < steps.length && (
<StepperSeparator className="max-md:mt-3.5 md:mx-4" />
)}
</StepperItem>
))}
</Stepper>
</div>
);
}
Inline titles and descriptions
import {
Stepper,
StepperDescription,
StepperIndicator,
StepperItem,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from "mako-ui/components/stepper";
const steps = [
{ description: "Sign in or register", step: 1, title: "Account" },
{ description: "Address and shipping", step: 2, title: "Delivery" },
{ description: "Card or invoice", step: 3, title: "Payment" },
];
export default function Particle() {
return (
<div className="mx-auto w-full max-w-xl">
<Stepper defaultValue={2}>
{steps.map(({ step, title, description }) => (
<StepperItem
className="not-last:flex-1 max-md:items-start"
key={step}
step={step}
>
<StepperTrigger className="rounded max-md:flex-col">
<StepperIndicator />
<div className="text-center md:text-start">
<StepperTitle>{title}</StepperTitle>
<StepperDescription className="max-sm:hidden">
{description}
</StepperDescription>
</div>
</StepperTrigger>
{step < steps.length && (
<StepperSeparator className="max-md:mt-3.5 md:mx-4" />
)}
</StepperItem>
))}
</Stepper>
</div>
);
}
Vertical
import {
Stepper,
StepperIndicator,
StepperItem,
StepperSeparator,
StepperTrigger,
} from "mako-ui/components/stepper";
const steps = [1, 2, 3, 4];
export default function Particle() {
return (
<Stepper defaultValue={2} orientation="vertical">
{steps.map((step) => (
<StepperItem className="not-last:flex-1" key={step} step={step}>
<StepperTrigger>
<StepperIndicator />
</StepperTrigger>
{step < steps.length && <StepperSeparator />}
</StepperItem>
))}
</Stepper>
);
}
Controlled vertical
- 1
- 2
- 3
- 4
"use client";
import { Button } from "mako-ui/components/button";
import {
Stepper,
StepperIndicator,
StepperItem,
StepperSeparator,
} from "mako-ui/components/stepper";
import { useState } from "react";
const steps = [1, 2, 3, 4];
export default function Particle() {
const [currentStep, setCurrentStep] = useState(1);
return (
<div className="flex flex-col items-center gap-8">
<Stepper
onValueChange={setCurrentStep}
orientation="vertical"
value={currentStep}
>
{steps.map((step) => (
<StepperItem className="not-last:flex-1" key={step} step={step}>
<StepperIndicator />
{step < steps.length && <StepperSeparator />}
</StepperItem>
))}
</Stepper>
<div className="flex gap-4">
<Button
className="w-32"
disabled={currentStep === 1}
onClick={() => setCurrentStep((step) => step - 1)}
variant="outline"
>
Previous
</Button>
<Button
className="w-32"
disabled={currentStep > steps.length}
onClick={() => setCurrentStep((step) => step + 1)}
variant="outline"
>
Next
</Button>
</div>
</div>
);
}
Vertical with inline titles
import {
Stepper,
StepperIndicator,
StepperItem,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from "mako-ui/components/stepper";
const steps = [
{ step: 1, title: "Account" },
{ step: 2, title: "Delivery" },
{ step: 3, title: "Payment" },
{ step: 4, title: "Review" },
];
export default function Particle() {
return (
<Stepper defaultValue={2} orientation="vertical">
{steps.map(({ step, title }) => (
<StepperItem
className="relative not-last:flex-1 items-start"
key={step}
step={step}
>
<StepperTrigger className="items-start rounded pb-12 last:pb-0">
<StepperIndicator />
<StepperTitle className="mt-0.5 px-2 text-start">
{title}
</StepperTitle>
</StepperTrigger>
{step < steps.length && (
<StepperSeparator className="absolute inset-s-3 top-[calc(1.5rem+0.125rem)] m-0 h-[calc(100%-1.75rem)] -translate-x-1/2" />
)}
</StepperItem>
))}
</Stepper>
);
}
Vertical with inline titles and descriptions
import {
Stepper,
StepperDescription,
StepperIndicator,
StepperItem,
StepperSeparator,
StepperTitle,
StepperTrigger,
} from "mako-ui/components/stepper";
const steps = [
{ description: "Sign in or register", step: 1, title: "Account" },
{ description: "Address and shipping", step: 2, title: "Delivery" },
{ description: "Card or invoice", step: 3, title: "Payment" },
];
export default function Particle() {
return (
<Stepper defaultValue={2} orientation="vertical">
{steps.map(({ step, title, description }) => (
<StepperItem
className="relative not-last:flex-1 items-start"
key={step}
step={step}
>
<StepperTrigger className="items-start rounded pb-12 last:pb-0">
<StepperIndicator />
<div className="mt-0.5 flex flex-col gap-0.5 px-2 text-start">
<StepperTitle>{title}</StepperTitle>
<StepperDescription>{description}</StepperDescription>
</div>
</StepperTrigger>
{step < steps.length && (
<StepperSeparator className="absolute inset-s-3 top-[calc(1.5rem+0.125rem)] m-0 h-[calc(100%-1.75rem)] -translate-x-1/2" />
)}
</StepperItem>
))}
</Stepper>
);
}
On This Page