Patterns

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.

Updated Jul 31, 2026 by Leonardo Posada

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

$90.52BUSD
  1. 1
    Header — 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.

  2. 2
    Header — Title

    Card title. text-base (16px) font-semibold text-card-foreground leading-6. Never a caption — this IS the title of the chart block.

  3. 3
    Header — 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 />.

  4. 4
    Main — 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.

  5. 5
    Main — 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).

  6. 6
    Main — 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 typeBar / Line toggleEye-hideView 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

Basic — title + chart

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>
With KPI header

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

$90.52BUSD
<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>
Canonical: KPI + trend (toggle + eye)

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

$90.52BUSD
<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>
Canonical: composite dual-axis (eye only)

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>
Canonical: multi-series line (eye only)

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

Succeeded
$—
Declined
$—
Refunded
$—
Error
$—
Pending
$—
Created
$—
<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>
Canonical: stacked bar

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

Succeeded / authorized
Declined / error
Fraud or 3DS declined / error
<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>
Canonical: donut with View more (drilldown)

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

DonutCard body (pattern lives outside ChartCard)
<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.

The ChartCard is a wrapper — pass any <ChartContainer> composition as children. Chart tokens (--chart-1..20) and the Yuno tooltip chrome from the Chart atom flow through automatically.
import { ChartCard, ChartCardControls } from "@/components/patterns/chart-card";

Props

Everything else is forwarded to the underlying elements via ...props.

PropTypeDefaultDescription
titlestring (required)Card title. Rendered as <Title as="h3"> at the default size (text-base font-semibold text-foreground).
infostringTooltip 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.
valuestringBig KPI value at the top of Main (text-3xl bold). Omit to skip the KPI row.
suffixstringSuffix next to the value (same text-3xl, normal weight). e.g. "USD", "BRL".
actionsReactNodeRight-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.
childrenReactNode (required)Main body content. Compose ticket wrapper / legend chips / <ChartCardControls /> / chart body in visual order (Main uses flex-col gap-4).
classNamestringExtra classes on the outer Card.

ChartCardControls (subcomponent)

PropTypeDefaultDescription
variantSwitcherbooleanfalseShow 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') => voidFires when the toggle changes.
onHide() => voidHandler for the eye-hide button. Omit to skip the eye button.
classNamestringExtra classes on the row wrapper.

Related

Cross-links to the atoms this molecule composes and sibling patterns.

  • ChartEvery ChartCard wraps a Chart. Palette + tooltip default flow through.
  • KPI cardFor a single-metric block with no chart body, use KpiCard. ChartCard is the chart-carrying sibling.
  • TooltipThe (i) info icon mounts a Tooltip on hover. Configure content via the `info` prop.