Chart card
The canonical Yuno chart-block chrome, extracted from the production Dashboard. A Card wrapping (1) an optional KPI header row with title + info icon + big value + suffix + subtitle metadata, (2) an actions row on the right of the header with an optional Bar/Line variant switcher and an optional eye-hide toggle, and (3) the chart body as children. Use this molecule around any Chart recipe to get the Yuno chrome (info tooltip, KPI header, variant toggle, eye-hide) for free.
Anatomy
Two stacked sections with zero gap between them. HEADER (p-6): the Info icon on the LEFT of the title (gap-4 / 16px between icon and text) + an actions slot on the RIGHT — canonically a shadcn <Button variant="link" size="sm">View more</Button>, and ONLY on cards whose production counterpart has that button. MAIN (p-6, gap-4 between children): the big KPI value + suffix (both text-3xl) at the top, then whatever you compose — ticket wrapper, legend chips, <ChartCardControls /> (the toggle + eye row that MUST live inside Main, not in the header), and finally the chart body.
Daily total sales volume
- 1Header — Info icon (left)
Optional Info icon (16px, weight light) that mounts a Tooltip on hover. Sits to the LEFT of the title with gap-4 / 16px. Pass the `info` prop to enable it.
- 2Header — Title
Card title. text-base (16px) font-semibold text-card-foreground leading-6. Never a caption — this IS the title of the chart block.
- 3Header — Actions slot (right)
Right-aligned slot. Canonical usage: <Button variant="link" size="sm">View more</Button>. Do NOT put toggle/eye here — those belong inside Main via <ChartCardControls />.
- 4Main — KPI value + suffix
Optional big value (text-3xl, font-bold) + suffix (text-3xl, font-normal). Both use text-foreground. Omit the `value` prop to hide the entire row.
- 5Main — Composable children
The rest of Main is `children`. Compose your ticket wrapper / legend chips / <ChartCardControls /> / chart body in the visual order you want, top to bottom (Main uses flex-col gap-4).
- 6Main — ChartCardControls row
Right-aligned row of chart controls: eye-hide + separator + Bar/Line toggle. Renders INSIDE Main, just above the chart body — never in the top Header. Import as a separate subcomponent and place it as one of the children.
Controls per chart type
Yuno production has a canonical rule for which top-right controls appear on each chart type. Do NOT invent combinations. The matrix below is enforced across the Dashboard — copy the recipe that matches the chart you are drawing.
| Chart type | Bar / Line toggle | Eye-hide | View more |
|---|---|---|---|
KPI + trend (single series bar/line) The standard Insights time-series card. Toggle lets the reader swap between bar and line for the same metric. No View more — trend cards are direct visualisations. | |||
Composite dual-axis (KPI + combined series) Payments + Conversion rate side-by-side (bar + line on two Y-axes). The chart type is fixed — no toggle. | |||
Multi-series line (status distribution, providers) Six statuses or many providers as separate lines. Bar variant would be unreadable — no toggle. | |||
Total volume composite (selectable KPIs + chart) The Volume tab hero: 6 clickable KPI cells above; the chart reflects the active KPI. Toggle allowed because the chart is a single series once a KPI is picked. | |||
Donut with drilldown available (Top issuers, Top providers, Top card brands) Only THESE three donuts show View more in prod. All three have a drilldown page — that is what View more opens. | |||
Donut without drilldown (Top payment methods, Top card types, Top countries) Same visual as the drilldown donuts but NO View more in prod — because there is no drilldown page prepared. Do not add it here. | |||
Stacked bar (daily total / unique customer payments) Prod does NOT show View more here despite the temptation. Legend row lives inside Main above the chart. No controls in the top Header. |
Recipes
The minimal wrapper. Just a title above the chart body. Use when the surrounding page already carries the KPI or when the chart is just illustrative.
Daily total sales volume
<ChartCard title="Daily total sales volume">
<ChartContainer config={config} className="h-64 w-full">
<BarChart data={data}>{/* … */}</BarChart>
</ChartContainer>
</ChartCard>Adds the big value + suffix + optional subtitle metadata. The most common Dashboard shape: 'Daily total sales volume · $90.52K USD · Maximum ticket: USD 65,121'. Info icon shows a Tooltip on hover with the metric definition.
Daily total sales volume
<ChartCard
title="Daily total sales volume"
info="Total sales value across all payment methods for the selected period."
value="$90.52B"
suffix="USD"
>
<ChartContainer config={config} className="h-64 w-full">
<BarChart data={data}>{/* … */}</BarChart>
</ChartContainer>
</ChartCard>The Yuno Insights time-series card. KPI header on the left, Bar/Line toggle + eye-hide on the right. Use for any single-series metric (sales volume, approved payments, average ticket). Do NOT add View more — trend cards are direct visualisations, not drilldowns.
Daily total sales volume
<ChartCard
title="Daily total sales volume"
info="Sum of processed volume per day."
value="$90.52B"
suffix="USD"
>
<ChartCardControls variantSwitcher defaultVariant="bar" onHide={() => {}} />
<ChartContainer config={config} className="h-64 w-full">
<BarChart data={data}>{/* … */}</BarChart>
</ChartContainer>
</ChartCard>Dual-axis charts like Payments + Conversion rate use a fixed chart type (bar + line on two axes). The Bar/Line toggle is meaningless here, so omit variantSwitcher. Only the eye-hide button remains.
Daily total payments and conversion rate
<ChartCard
title="Daily total payments and conversion rate"
info="Payments per day paired with conversion rate."
>
{/* Legend chips (Payments + Conversion rate) go here as the first children */}
<ChartCardControls onHide={() => {}} />
<ChartContainer config={config} className="h-64 w-full">
<ComposedChart data={data}>
<Bar yAxisId="left" dataKey="payments" fill="var(--color-payments)" barSize={24} />
<Line yAxisId="right" dataKey="conversion" stroke="var(--color-conversion)" />
<ChartBrush dataKey="day" />
</ComposedChart>
</ChartContainer>
</ChartCard>Status distribution and provider comparison charts render many lines at once. A bar variant would be unreadable, so no toggle. Legend chips go above the chart body; only eye-hide sits top-right.
Volume status distribution
<ChartCard
title="Volume status distribution"
info="Processed volume split by payment status."
>
{/* Legend chips (Succeeded / Declined / Refunded / Error / Pending / Created) */}
<ChartCardControls onHide={() => {}} />
<ChartContainer config={config} className="h-64 w-full">
<LineChart data={data}>
{/* one <Line/> per status */}
<ChartBrush dataKey="day" />
</LineChart>
</ChartContainer>
</ChartCard>Daily total payments and unique customer payments render as stacked bars (succeeded / declined / fraud). No toggle. Only eye-hide inside Main. Prod does NOT show View more on these — do not add it.
Daily total payments
<ChartCard
title="Daily total payments"
info="Stacked breakdown by payment status."
>
{/* Stacked legend row (Succeeded / Declined / Fraud or 3DS declined) */}
<ChartCardControls onHide={() => {}} />
<ChartContainer config={config} className="h-64 w-full">
<BarChart data={data}>
<Bar dataKey="succeeded" stackId="a" barSize={32} />
<Bar dataKey="declined" stackId="a" barSize={32} />
<Bar dataKey="fraud" stackId="a" barSize={32} />
<ChartBrush dataKey="day" />
</BarChart>
</ChartContainer>
</ChartCard>The three donut cards that have a real drilldown in prod (Top issuers, Top providers, Top card brands) show a View more link in the top-right of the Header via the `actions` slot. Pass a shadcn <Button variant="link" size="sm">View more</Button>. Do NOT add View more to any other card just because it looks tidy — prod is intentional about this.
Top issuers
<ChartCard
title="Top issuers"
info="Top issuing banks by volume."
actions={
<Button variant="link" size="sm" className="px-3">
View more
</Button>
}
>
{/* donut + legend list */}
</ChartCard>Import
Copy this import line at the top of the file where you compose this molecule.
import { ChartCard, ChartCardControls } from "@/components/patterns/chart-card";Props
Everything else is forwarded to the underlying elements via ...props.
| Prop | Type | Default | Description |
|---|---|---|---|
| title | string (required) | — | Card title. Rendered as <Title as="h3"> at the default size (text-base font-semibold text-foreground). |
| info | string | — | Tooltip text on the Info icon. The icon sits inside <Title>'s left icon slot (gap-3 / 12px between icon and title text — Yuno heading canon). Omit to hide the icon. |
| value | string | — | Big KPI value at the top of Main (text-3xl bold). Omit to skip the KPI row. |
| suffix | string | — | Suffix next to the value (same text-3xl, normal weight). e.g. "USD", "BRL". |
| actions | ReactNode | — | Right-side slot in the Header. Canonical usage: <Button variant="link" size="sm">View more</Button>. Only for cards whose prod counterpart has that button — see the controls matrix. |
| children | ReactNode (required) | — | Main body content. Compose ticket wrapper / legend chips / <ChartCardControls /> / chart body in visual order (Main uses flex-col gap-4). |
| className | string | — | Extra classes on the outer Card. |
ChartCardControls (subcomponent)
| Prop | Type | Default | Description |
|---|---|---|---|
| variantSwitcher | boolean | false | Show the Bar/Line toggle. State is managed internally; read it via variant + onVariantChange. |
| variant | 'bar' | 'line' | — | Controlled variant. Only relevant when variantSwitcher is true. |
| defaultVariant | 'bar' | 'line' | 'bar' | Uncontrolled default variant. |
| onVariantChange | (v: 'bar' | 'line') => void | — | Fires when the toggle changes. |
| onHide | () => void | — | Handler for the eye-hide button. Omit to skip the eye button. |
| className | string | — | Extra classes on the row wrapper. |
Related
Cross-links to the atoms this molecule composes and sibling patterns.