# Compact Steps

> 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.

A shadcn/ui block from the [@01orbital](https://01orbital.dev/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/01orbital/compact-steps).

- Page, with a live preview: https://shadcn.exchange/r/01orbital/compact-steps
- Screenshot: https://shadcn.exchange/shot/01orbital/compact-steps/light.png
- Registry item: https://01orbital.dev/r/compact-steps.json
- Categories: stepper
- Listed: 2026-11-01, source last changed 2026-11-01

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/01orbital/compact-steps.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@01orbital/button`, `card`, `@01orbital/field`, `@01orbital/input`, `@01orbital/stepper`, `@01orbital/textarea`

## Files

### `blocks/stepper/compact-steps/index.tsx`

```tsx
"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>
  )
}
```
