Skip to content

Step Cards

A row of cards where the open one takes the free width and the closed ones narrow to their number.

$npx shadcn@latest add https://framebrick.co/r/step-cards.json

From the Ciketo template, see it in context.

  1. Tell us what you need

    Rooms, budget, the streets you already like — a short call is enough to start.

    Week one

    A written brief within two days, and a fixed price before anything starts.

    Open living room with a long grey sofa
  2. Tour the shortlist

    We visit five homes at most, always with the paperwork checked beforehand.

    Weeks two and three

    Every viewing comes with a one-page summary of what we found.

    Modern dark-clad house lit up at dusk under a large tree
  3. Sign and move in

    We handle the offer, the survey and the notary, and hand you the keys.

    Week six

    One person from start to finish, whose number you already have.

    Bright open living room with a staircase and a blue sofa

Installation

$ npx shadcn@latest add https://framebrick.co/r/step-cards.json

Usage

import { StepCards } from "@/components/ui/step-cards";

<StepCards
  label="How it works"
  steps={steps.map((s) => ({
    value: s.slug,
    title: s.title,
    description: s.text,
    label: s.when,
    note: s.detail,
    media: <Image src={s.photo} alt="" fill />,
  }))}
/>

Props

PropTypeDefaultDescription
steps{ value, title, description?, label?, note?, media? }[]—The cards. An open card shows the number, title and description at the top, and a rule, a label with a note and a 3:2 photo at the bottom.
labelstring—Accessible name of the row.
valuestring—Open card (controlled).
defaultValuestringfirst cardCard that starts open (uncontrolled).
onValueChange(value: string) => void—Called when another card opens.
heightnumber600Height of the row from 768 px up, in px. The open card has to fit in it: 600 holds a 3:2 photo 450 px wide with a two-line description and note.
collapsedSizenumber105Width of a closed card from 768 px up, in px. Under about 100 the "01 / 03" no longer fits beside the card's padding.
  • One card is always open: a click on a card or its number opens another, and a click on the open one does nothing. No timer, no hover.
  • The row has a fixed height, as in Framer, and the open card does not shrink its photo to fit: parts taller than the row are clipped at the bottom. Give shorter content a shorter row, or leave the photo out.
  • Below 768 px it is a column, where closed cards keep their number and title and open by height.
  • Closed cards sit at 70% and their text carries its own tone: a dimmed card multiplies its text colour by the opacity, and muted-foreground would fall to 2.7:1. Ciketo dims to 60%.
  • The details of a closed card stay in the HTML but are inert (Ciketo removes them, so the text is gone for search engines). The number is a real button with aria-expanded, named by the title.
  • Reduced motion is a zero-duration transition, not different markup: the server cannot know it, and markup that depended on it would fail hydration. Flex-grow is not a transform, so MotionConfig would not skip it.

AI prompt

Paste into v0, Lovable, Bolt or Claude to rebuild this component in your own project.

Create a React + Tailwind CSS + Motion (motion/react) component called StepCards.
- An ol with aria-label. From 768px up a row of fixed height (default 600px) with gap-2.5; below that a column.
- Each card is a motion.li: rounded-lg border bg-card p-5, overflow-hidden, flex column. At the top the number ("01" in
  text-lg font-semibold, then "/ 04" in text-sm) as a real button with aria-expanded, then the title (h3 font-medium) and
  a description. At the bottom (from 768px, justify-between) a 1px rule, a small label, a note and a 3:2 photo (max 450px).
- Exactly one card is open (the first by default). Clicking a card, or pressing its number button, opens it — put the
  click handler on the li and let the keyboard's button click bubble to it. Clicking the open card does nothing.
- The open card animates flex-grow to 1; closed cards to 0 with a fixed flex-basis (default 105px) so they narrow to
  their number. Closed cards fade to opacity 0.7 and their text uses text-foreground/95 (the opacity multiplies the colour;
  muted-foreground would fall to 2.7:1). Spring: bounce 0, duration 0.4s, for the width and the fade.
- The description, rule, note and photo fade with opacity and are inert when closed. Below 768px they also open by height
  with a CSS grid-template-rows 0fr → 1fr transition; from 768px they keep their height and are clipped by the narrow
  card. From 768px a closed card shows only its number, so the title is md:hidden and the button gets an sr-only title.
  Keep the number on one line (whitespace-nowrap): at 105px a closed card has 63px of room for "01 / 04".
- The row has a fixed height and does not shrink the photo: parts taller than the row are clipped at the bottom.
- Reduced motion: use useReducedMotion() only to switch the transition to zero duration — never to change the markup,
  because the server cannot know it and a different render is a hydration mismatch.

Be the first to know

Get new templates and components first

One short email when we release a template or a free component. No spam — unsubscribe anytime.

We use your email only for Framebrick updates. Privacy policy