Skip to content
Sign in
Apps SDKRecipe cardMenu

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.

ProBacalhau à Brás scaled from four to six: every amount rescales, in the list and inside the method.

Installation

$ pnpm dlx shadcn@latest add @chatcn-pro/recipe-card

Usage

tsx
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

ProHalved to two, a shopping list that fails when sent, a long title with no timers, loading and an error.

Props

PropTypeNotes
title, metaReactNode, ReactNode[]The header
image{ src, alt }Optional band above the header
baseServingsnumberWhat the amounts are written for
servings, defaultServings, onServingsChangenumberThe 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
localestringNumber format. Default en-GB
loading, errorbooleanBones, or the error notice with onRetry
onRetry, onExpand() => void
labelsPartial<…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

KeyAction
TabServings, each ingredient, each timer, then the list button
Space on an ingredientTick or untick it
Enter or Space on a timerStart, 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.