---
title: "Dashboard & charts"
description: "We wrap a charting engine (Recharts, or visx where we need more control) instead of building one. What we add is everything a chart library doesn't know about money. Specs for 11 React components: DashboardShell, KpiRow, MoneyChart, MoneyBarChart and 7 more."
canonical: "https://lopifi.com/docs/catalog/dashboard-charts"
---

# Dashboard & charts

> We wrap a charting engine (Recharts, or visx where we need more control) instead of building one. What we add is everything a chart library doesn't know about money. Specs for 11 React components: DashboardShell, KpiRow, MoneyChart, MoneyBarChart and 7 more.

Source: https://lopifi.com/docs/catalog/dashboard-charts
Part of: Lopifi — Planned: US GAAP books on autopilot, checked by a CPA (https://lopifi.com)

---

# Dashboard & charts

We wrap a charting engine (Recharts, or visx where we need more control) instead of building one. What we add is everything a chart library doesn't know about money.

**Two rules govern the family.** Under [Money Law 2](https://lopifi.com/md/docs/motion/principles#law-2--data-does-not-re-animate), a chart animates on first paint and never on data change. And every chart has an accessible table equivalent.

---

## 71. `DashboardShell`
12/8/4-column responsive grid with optional user rearrangement. Widget positions persisted per user. Drag uses FLIP + `spring.snap`, measured once at drag start.

Equal-height rows via `auto-rows-fr` keep figure baselines aligned across a row. Misaligned baselines are the most visible failure on a dashboard, and the fix is one line.

## 72. `KpiRow`
2–4 `KpiCard`s with a shared figure baseline. **Forces Inter** on all figures regardless of the `family` prop, because they align across the row. The parent enforces it; the caller can't override it.

---

## 73. `MoneyChart`

Line and area, with everything a generic chart library gets wrong about money:

- **Money-formatted axis**, abbreviated (`$1.2M`) with the exact value in the tooltip. Abbreviation is allowed here; see [numerics](https://lopifi.com/md/docs/foundations/numerics#abbreviation).
- **Zero baseline is emphasized**: a 1px `--border-strong` line, distinct from the `--border-subtle` gridlines. When data crosses zero, it's the most important line in the chart.
- **Negative regions stand out**: a fill below zero at `--negative` with a 45° hatch, so it survives grayscale.
- **Y-axis starts at zero** for quantities. The only sanctioned exception is a rate chart (`CurrencyPairChart`), and there the axis is explicitly labeled.
- **Projections are dashed.** A forecast drawn like actuals is a lie. The boundary is drawn too, as a dashed vertical rule labeled `Forecast →`, because you can't read a date off a dash pattern spread over six months.
- **Gridlines: horizontal only**, `--border-subtle`. Vertical gridlines on a time series are noise.
- **Area fill** is the one sanctioned gradient: a single hue fading to transparent, which keeps the line readable over gridlines. It's there for legibility, not decoration.
- Multi-currency series is **rejected at the type level**. Convert to a stated presentation currency first, and label the axis with it.

### The FP&A layer

A plan chart has to say four things a line chart can't. Leaving any of them out is a way of being wrong that a prettier chart hides.

- **The range, and what it is.** `band` takes a low, a high and a **required** `label` (`P10–P90`, `±1σ`, `Downside to upside`). A shaded cone with no stated interval claims a precision nobody gave it, and a reader who can't tell which interval it is will assume whichever suits them. The band gets a hairline edge as well as a fill, because a fill alone disappears on a projector and the edges are what people quote. It widens with distance, or it's lying about what can be known.
- **The alternatives, unranked.** `scenarios` draws base, upside and downside from the same anchor, told apart by **dash rhythm first and color second**. In red-amber-green, a third of readers can't separate the three lines. No best-case styling and no ordering by outcome. A scenario is a set of assumptions, and calling one the good one turns a forecast review into a negotiation over which line goes to the board.
- **The plan.** `plan` is a *reference*, not a series: dotted, in the neutral tertiary tone, with no viz color and no place in the legend's isolate control. A budget is the line the business agreed to, not something that happened. A series color would put it in competition with the measure it's meant to be read against.
- **The variance, in words that survive grayscale.** Stated as **F** and **U**, not a color or a bare sign. Favorable and unfavorable are the terms of art and stay unambiguous under polarity: a cost under plan is F and revenue under plan is U, yet *both are negative absolute variances*. Red and green can't carry that. `plan.polarity` is required for the same reason as everywhere else. A chart doesn't know its own polarity, and guessing gets half of FP&A backwards.

The variance is always taken against the **first** series declared, and the readout disappears when that series is isolated away. A variance whose base silently changes as the reader filters is worse than none, because the number moves and nothing on the chart says why.

The band, the scenarios and the plan all sit inside the y-domain. A cone clipped by the axis quietly crops the bad case out of the picture.

**The table equivalent carries all of it**: a `Basis` column marking each period actual or forecast, the band's range, each scenario, the plan and the variance. A table of base values alone isn't the chart in another form. It's a different, more confident chart.

**Motion:** `stroke-dashoffset` draw, 400ms `--ease-out`, once on mount or scroll-into-view. Instant on data change.

**Touch:** 44px-tall scrub target across the plot, with a **persistent readout** in a fixed header, not a floating tooltip under the finger.

## 74. `MoneyBarChart`
Grouped and stacked.

- **`transform-origin` at the zero baseline**, not the bottom. Otherwise negative bars grow up from the floor and briefly show the wrong sign, which is a real correctness bug.
- Negative bars hatched.
- Stacked totals labeled at the bar end.
- Bar radius 4 on the outer corners only, and **square below 12px bar width**, since a rounded 6px bar is a blob.
- Draw-in: `scaleY 0→1`, 400ms, stagger 30ms capped at 240ms. First paint only.

## 75. `WaterfallChart`

> The P&L bridge. Essential in finance, and nearly absent from open-source charting.

Start → increases → decreases → end, with connector lines between bars. Increases `--positive`, decreases `--negative`, subtotals neutral, **with all three told apart by position and connector, not color alone.**

**Every subtotal and end bar is checked against the running sum** of the steps before it. A bar that misses is plotted as stated — the caller's figure is the claim — and the break is said in words with an icon under the chart: "Net income doesn’t tie by $1,200.00: the steps sum to …, the bar states …".

The most useful chart in the family. It answers "why did net income change?" in one image.

## 76. `CategoryBreakdown`
Ranked horizontal bars with values and percentages. **Not a pie chart.** People compare lengths well and angles badly; a pie with more than three slices is decoration.

Top N + "Other" (max 6 series). Percentages use largest-remainder allocation so they sum to exactly 100%.

## 77. `CashCalendar`
Daily cash movement as a heatmap. Sequential OKLCH lightness ramp within one hue for magnitude, with the diverging in/out distinction carried by **two separate ramps that differ in lightness**, not by red/green.

## 78. `CandlestickChart`
For FX and crypto pairs. Up and down candles differ in **fill** (hollow vs. solid) as well as color. That's the traditional convention, and it's also the right accessibility answer.

## 79. `ChartLegend`
Interactive: click to isolate a series, keyboard-navigable, `aria-pressed` state. Positioned below on phone as a wrapping row. Series colors are assigned by a **stable key hash**, never by array index, so a category is the same color in the dashboard and the drill-down.

## 80. `ChartTooltip`
Full values, never abbreviated. All series at the hovered point, in the same order as the legend. Touch-persistent variant. Follows the pointer with no animation, because a tooltip that eases behind the cursor feels broken.

## 81. `DataTableToggle`

> Every chart has an accessible table equivalent, reachable by a visible toggle.

WCAG requires it, and it's also what power users want most: the actual numbers. Serving both with one component is why it's P1, not P3.
