CardHunt
CardHunt is the whole experience in one component. Cards hide in a grid. A button starts a scan that finds them one by one, each with the reveal its tier calls for. Afterwards the panel stays: the finds are listed, each can be looked at again, and the scan can be run again from a blank grid.
5 cards are hiding in here.
Run the scan. Each card opens where the beam finds it.
- Still hidden
- Still hidden
- Still hidden
- Still hidden
- Still hidden
Five cards are in there. Press the button.
Usage
import { useOpenedCards } from "@react-cards-opening/core";
import { CardHunt, themeOf } from "@react-cards-opening/ui";
function Hunt({ cards, activity }) {
const { opened, open } = useOpenedCards("deal:ada:2026");
return (
<CardHunt
cards={cards}
opened={opened}
onOpen={open}
levels={activity} // 0 to 4, one per cell
theme={themeOf("grape", "dark")}
describeSpot={(cell) => `day ${cell + 1}`}
sessionKey="deal:ada:2026:spots"
/>
);
}The grid
levels is the grid: one number per cell, 0 for unlit up to 4. Cells fill top to bottom, then left to right, seven to a column unless rows says otherwise. Cards hide on lit cells. A year of activity is the obvious grid, but it can be any data that makes a shape: a season of matches, a board, a month of orders.
Props
| Prop | Type | What it does |
|---|---|---|
cards | readonly C[] | Every card in the deal, found or not. Each needs a name. |
opened | readonly string[] | Ids already opened. |
onOpen | (id: string) => void | A card was turned face up for the first time. |
levels | readonly number[] | The grid: one number per cell, 0 to 4. |
lead? | number | Empty slots before the first cell, to line a calendar up with its weekday. |
rows? | number | Cells in a column. 7 by default. |
theme? | CardTheme | The colours. |
rarities? | readonly RarityTier[] | Your tiers. |
describeSpot? | (cell: number) => string | How a cell reads on a card and a badge: "Mar 14". |
actions? | ReactNode | Extra buttons in the header, next to the scan button. |
sessionKey? | string | null | Where this visit remembers each card's cell, so replays reopen the same tiles. |
hero? | boolean | The hunt is the page: bigger type, and it fills the box it is given. |
onRunChange? | (running: boolean) => void | A scan started or came to rest. |
labels? | Partial<CardHuntLabels> | Every word in the panel. |
effects? | EffectPlan | false | What each tier plays on top of the card's own reveal. The signature shows when left out, false for none. |
landing? | (card) => HTMLElement | null | Where a found card lands instead of its badge. Answer null and it fades out. |
renderFace? | (card, info) => ReactNode | The front of the card. |
renderBack? | (card) => ReactNode | The back of the card. |
readingOf? | (card, info) => readonly string[] | The text on a face of your own. |
className? | string | Added to the panel. |
classNames? | CardHuntClassNames | A class of your own for each part: the panel's, the grid's and the reveal's. See Styling by hand. |
style? | CSSProperties | Added to the panel. |
As the page
With hero, the hunt is what the visitor came for. The headline is larger, and the panel fills the height of its container with the grid in the middle. Give the container a height.
2 cards are hiding in here.
Run the scan. Each card opens where the beam finds it.
- Still hidden
- Still hidden
What the buttons do
| Button | When | What it does |
|---|---|---|
| Scan | Cards are still hidden | Finds them one after another. |
| Find all | During a scan with more to come | Skips the show and opens the rest at once. |
| Scan again | Everything is found | Replays from a blank grid. Each card comes out of the same tile. |
| A badge or a lit cell | After a find | Brings that card up again. |
Your own words
Every line in the panel comes from labels. The headline and the subline are functions of where the hunt stands, so they can count. Labels and translation lists them all.
<CardHunt
labels={{
headline: ({ pending, running }) => (running ? "Looking..." : `${pending} left to find`),
scan: () => "Search the board",
found: (where) => `Was on ${where}`,
}}
{...rest}
/>