Small new-project card that walks three steps (name, details, access) with a row of dot indicators and the current step title beside them, for dialogs where a full stepper is too loud.
"use client"
import { useId, useState } from "react"
import {
Stepper,
StepperContent,
StepperIndicator,
StepperItem,
StepperList,
StepperTitle,
StepperTrigger,
} from "@/components/stepper/stepper"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Field, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
// The copy: change text here, not in the markup.
const COPY = {
title: "New project",
body: "A name, a description, who can see it.",
back: "Back",
next: "Next",
create: "Create project",
steps: ["Name", "Details", "Access"],
name: "Project name",
desc: "Description",
access: "Who can see it",
accessHint: "Everyone in Orbcore Design. Change this per project later.",
created: "Project created. Opening it now.",
} as const
// The small one: three dots and the current title in the footer of a dialog
// or card, under the content, where a full stepper would be too loud.
export default function CompactSteps() {
const id = useId()
const [step, setStep] = useState(0)
// Local stand-in: a real block posts the fields and routes to the project.
const [created, setCreated] = useState(false)
const last = COPY.steps.length - 1
return (
<Card className="mx-auto w-full max-w-sm">
<CardHeader>
<CardTitle>{COPY.title}</CardTitle>
<CardDescription>{COPY.body}</CardDescription>
</CardHeader>
<CardContent>
<Stepper value={step} onValueChange={setStep} className="gap-5">
<StepperContent keepMounted value={0}>
<Field>
<FieldLabel htmlFor={`${id}cs-name`}>{COPY.name}</FieldLabel>
<Input id={`${id}cs-name`} placeholder="Registry refresh" />
</Field>
</StepperContent>
<StepperContent keepMounted value={1}>
<Field>
<FieldLabel htmlFor={`${id}cs-desc`}>{COPY.desc}</FieldLabel>
<Textarea id={`${id}cs-desc`} rows={3} />
</Field>
</StepperContent>
<StepperContent keepMounted value={2}>
<p className="text-sm font-medium">{COPY.access}</p>
<p className="text-sm text-muted-foreground" aria-live="polite">
{created ? COPY.created : COPY.accessHint}
</p>
</StepperContent>
<div className="flex items-center">
<StepperList className="w-auto gap-1.5">
{COPY.steps.map((title, i) => (
<StepperItem key={title} value={i} className="flex-none">
<StepperTrigger disabled={created} className="size-auto p-1">
<StepperIndicator className="size-2 bg-border ring-0 data-[state=active]:w-5 data-[state=active]:bg-foreground data-[state=active]:ring-0 data-[state=completed]:bg-foreground/50" />
<StepperTitle className="sr-only">{title}</StepperTitle>
</StepperTrigger>
</StepperItem>
))}
</StepperList>
<span className="ml-2 text-xs text-muted-foreground">
{COPY.steps[step]}
</span>
</div>
</Stepper>
</CardContent>
<CardFooter className="justify-between">
<Button
variant="outline"
size="sm"
disabled={step === 0 || created}
onClick={() => setStep(step - 1)}
>
{COPY.back}
</Button>
<Button
size="sm"
disabled={created}
onClick={() => (step < last ? setStep(step + 1) : setCreated(true))}
>
{step < last ? COPY.next : COPY.create}
</Button>
</CardFooter>
</Card>
)
}
pnpm dlx shadcn add @01orbital/compact-stepsnpx shadcn add @01orbital/compact-stepsyarn shadcn add @01orbital/compact-stepsbunx --bun shadcn add @01orbital/compact-stepsFree under the MIT license. Add the @01orbital registry to components.json once (install guide), then run the command.
It lands at components/blocks/stepper/compact-steps/index.tsx, as source you own and edit.
import CompactSteps from "@/components/blocks/stepper/compact-steps"
export default function Example() {
return <CompactSteps />
}Registry pieces it installs with it:
buttoncardfieldinputtextareaCheckout Bars
Bars twin of Checkout: the same four steps validated with react-hook-form and zod, but progress is a row of rail segments with screen-reader titles instead of numbered triggers.
Domain Setup
Custom-domain card in three numbered steps: enter the domain, copy the A and CNAME records from a monospace block, then verify with a pending or active badge. The check is a stand-in.