StepCard
One step of a short flow on a frosted card — joining a studio after an invite. The studio's mark and where you are at the top, the step's question as a display title with a hint, what the step asks for in between, and its buttons at the foot. It fills a BookingStage's card: the head and the buttons stay put, and only the body scrolls.
Is this you
The first step of joining asks one thing, so the title is the whole step and the body stays empty. The hint says which address the invite went to; the way out sits first, the way on last.
Belaze invited you to join Okami as Pele.
Sent to pele@belaze.sk.
import { StepCard } from "@/components/shared/step-card";
import { BookingStage } from "@/components/shared/booking-stage";
import { StudioMark } from "@/components/shared/studio-mark";
import { Button } from "@/components/shared/button";
<BookingStage className="h-dvh">
<StepCard
mark={<StudioMark name="Belaze" logo={logo} tone="plain" />}
step="1 of 4"
title="Belaze invited you to join Okami as Pele."
hint="Sent to pele@belaze.sk."
actions={
<>
<Button variant="ghost" size="lg" onClick={askNotMe}>That's not me</Button>
<Button size="lg" onClick={next}>Yes, that's me</Button>
</>
}
/>
</BookingStage>Your name
A step that asks for one value: the title names it, so the label is read, not shown, and the field is lg, the size of login's, where the field is the whole task.
Your name
How clients and the team see you.
import { StepCard } from "@/components/shared/step-card";
import { BookingStage } from "@/components/shared/booking-stage";
import { StudioMark } from "@/components/shared/studio-mark";
import { Button } from "@/components/shared/button";
import { Field, FieldLabel } from "@/components/shared/field";
import { Input } from "@/components/shared/input";
<StepCard
mark={mark}
step="2 of 4"
title="Your name"
hint="How clients and the team see you."
actions={
<>
<Button variant="ghost" size="lg" onClick={back}>Back</Button>
<Button size="lg" disabled={!name.trim()} onClick={next}>Continue</Button>
</>
}
>
<Field>
<FieldLabel htmlFor="join-name" className="sr-only">Your name</FieldLabel>
<Input id="join-name" size="lg" value={name} onChange={…} autoComplete="name" />
</Field>
</StepCard>A step that scrolls
Once a step holds more than the card has room for, its body scrolls between the head and the buttons, which never move. Give the body a scrollRef when a conversation in it follows its end, and pin a composer under the buttons with below.
Services you do
Booking and Okami offer only these.
- Haircut45 min
- Haircut + beard75 min
- Beard trim30 min
- Clipper cut30 min
- Kids' cut30 min
- Hot towel shave40 min
import { StepCard } from "@/components/shared/step-card";
import { BookingStage } from "@/components/shared/booking-stage";
import { StudioMark } from "@/components/shared/studio-mark";
import { Button } from "@/components/shared/button";
<StepCard
mark={mark}
step="3 of 4"
title="Services you do"
hint="Booking and Okami offer only these."
scrollRef={scroll}
below={<Composer … />}
actions={…}
>
<ServicesPicker services={services} selected={ids} onChange={setIds} />
{conversation}
</StepCard>API reference
| Prop | Type | Default | Description |
|---|---|---|---|
| mark | ReactNode | — | At the top left: whose flow it is, a StudioMark. |
| step | ReactNode | — | At the top right: where you are, “2 of 4”. Leave out for a flow of one step. |
| title | ReactNode | — | The step's question, as a display title. |
| hint | ReactNode | — | Under the title: what it's for. |
| level | 1 | 2 | 3 | 1 | The title's heading level: 1 when the card is the page. |
| actions | ReactNode | — | The buttons, set to the right: the way back first, the way on last. |
| below | ReactNode | — | Pinned under the buttons: a composer. |
| scrollRef | RefObject<HTMLDivElement | null> | — | The body's scroll, for a conversation in it to follow its end. |
| children | ReactNode | — | What the step asks for; Okami's words can follow. |
Guidelines
- Put it in a BookingStage: the stage's frosted card is the column it fills, and the card hugs the step up to the screen.
- One question a step, asked in the title. If a step needs a label as well, it's asking two things.
- Both buttons are
lg, the ghost one first. Disable the way on until the step is answered, rather than erroring after.