Recipe cardPro
A recipe you can cook from in the thread: change the servings and every amount rescales, in the list and inside the method.
On this page
Installation
Usage
import { RecipeCard } from "@/components/ui/chatcn/pro/recipe-card"
<RecipeCard
title="Bacalhau à Brás"
meta={["35 min", "Easy"]}
baseServings={4}
ingredients={ingredients}
steps={[{ text: "Fry {potatoes} until pale gold.", timer: 360, timerLabel: "Fry" }]}
onAddToList={(items) => addToList(items)}
/>Amounts are written for baseServings. The stepper scales them into kitchen numbers: common fractions for spoons and cups (1½ tbsp), grams and millilitres rounded to a sensible step, and counted things such as eggs rounded up to a whole one when whole is set. An ingredient with no amount reads as to taste.
Steps reference ingredients as {id}, so the method says the scaled amount too: change four servings to six and step three reads 750 g. A step with timer (seconds) gets a timer you start, pause and reset; several run side by side, and each announces when it is done and calls onTimerDone.
Tick what you already have and the button sends the rest to onAddToList as plain lines ("750 g Waxy potatoes"). Return a promise to show the pending state; a rejection shows the fix in words. Render it inside the card your host draws for a widget answer. It fills the width it is given, so a comfortable max width (around 28 rem) comes from the card, and it follows the host theme through the kit tokens. Pass onExpand where the host can go full screen.
Examples
States
Props
| Prop | Type | Notes |
|---|---|---|
title, meta | ReactNode, ReactNode[] | The header |
image | { src, alt } | Optional band above the header |
baseServings | number | What the amounts are written for |
servings, defaultServings, onServingsChange | number | The stepper, 1 to 24 |
ingredients | { id, name, amount?, unit?, whole?, note? }[] | whole rounds counted things up |
steps | { text, timer?, timerLabel? }[] | {id} in text fills the scaled amount |
onAddToList | (items: string[]) => unknown | Promise<unknown> | The unticked lines |
onTimerDone | (stepIndex: number) => void | |
locale | string | Number format. Default en-GB |
loading, error | boolean | Bones, or the error notice with onRetry |
onRetry, onExpand | () => void | |
labels | Partial<…Labels> | Every string the widget writes, for translation |
Labels
labels covers the section names, servings(n), the stepper buttons, to taste, step(n), the timer controls (start(label, time), pause, resume, reset, timerDone(label)), addToList(n), the added and failed lines, have(name) and the error copy.
Keyboard
| Key | Action |
|---|---|
| Tab | Servings, each ingredient, each timer, then the list button |
| Space on an ingredient | Tick or untick it |
| Enter or Space on a timer | Start, pause or reset it |
Accessibility
- Ingredients are real checkboxes named with the scaled amount.
- The servings count is a polite live region; a finished timer is announced once, assertively, by name.
- Each step number is read as Step 1, Step 2, before its text.
- The timer's progress line is decorative; the time left is text.
Motion
Ticks fade their check mark in 150 ms. A running timer's progress line grows with the clock and nothing else moves.