The gap
@netscript/fresh-ui ships no charting primitives. The documented surface is
Icon, DataGrid, Dropzone, the toast helpers, Accordion / Dialog /
Drawer / Popover / Sheet / Tabs / Tooltip, and the L0 primitives.
Any consumer that needs to show a metric over time therefore hand-rolls one. We
just did this for a second time in eis-chat (rickylabs/eis-chat#237, a usage &
cost surface), and ended up authoring three primitives from scratch:
| Primitive |
What it does |
Why nothing existing covered it |
AreaChart |
area/line over an ordered series, with a per-point hover readout |
no time-series primitive exists |
ChartBlock stacked variant |
horizontal bars composed of separately-priced segments |
bar primitives were single-value |
RadialChart |
concentric rings, one series per ring |
a donut partitions ONE whole; this compares series on independent axes |
These are ~450 lines of SVG + CSS that are not app logic. They are a design
system's job, and every consumer is paying for them again.
Why a library isn't the answer
Not asking for a recharts dependency — the opposite. Deno + Preact islands, and
the dependency policy is Deno-native / @std → JSR → npm only as a stated
exception. A charting library is a large client bundle for what is, in the
common case, a few hundred lines of SVG driven by design tokens.
What's wanted is the same thing fresh-ui already does well for the other
primitives: token-driven, SSR-correct, theme-aware, no client JS unless the
interaction genuinely needs it.
What a useful primitive set looks like
From actually building these, the requirements that mattered:
- Tokens only, never a colour literal. These pages are theme-aware and a
literal breaks exactly one theme, silently. Intents through data-tone
against the semantic tokens (--ns-primary, --ns-success, …), same as the
rest of fresh-ui.
- SSR-correct on first paint, no island required. The hover readout on our
area chart is pure CSS (:hover on transparent hit-slots revealing a
sibling). No hydration cost, no first-paint mismatch. Anything that needs
client JS should be opt-in, not the baseline — we hit a real production bug
where a chart region that depended on hydration painted and then vanished.
- Accessible without hover. Hover-only detail is invisible to keyboard and
screen-reader users; the summary needs to be in aria-label / <title>.
- Formatting stays with the caller. Our charts take preformatted display
strings. A chart primitive should not know about currencies or locales —
ours renders CHF amounts converted at display time from stored USD, which is
not something a chart could infer.
- Empty and partial states are first-class. This surface makes claims about
money, so "no data" has to render as a labelled empty frame, never as a zero
and never as a vanished panel. A primitive that returns null on an empty
series pushes that decision onto every caller, and most will get it wrong.
- Stacked segments must stay separable. Our use case is cache-read vs
cache-write tokens, which are priced differently — collapsing them into one
bar makes the chart uncostable.
Suggested scope
A minimal @netscript/fresh-ui chart set that would have covered 100% of what
we needed:
<Chart variant="bar" | "column" | "stacked" | "line" | "area"> with
data-tone intents, nice-axis ticks, and an optional CSS-only hover readout
<Donut> (single ring, share of a whole) and <Radial> (concentric, series
on independent axes) — they answer different questions and shouldn't be one
component
- an
EmptyChart frame so "no data" renders axes + an explanation rather than
nothing
Happy to contribute ours upstream as a starting point if that's useful — they're
written to the @component / @layer / @depends theme-seed contract already.
Local implementations for reference: apps/dashboard/components/ui/area-chart.tsx,
radial-chart.tsx, and the stacked variant in chart-block.tsx in
rickylabs/eis-chat#237.
The gap
@netscript/fresh-uiships no charting primitives. The documented surface isIcon,DataGrid,Dropzone, the toast helpers,Accordion/Dialog/Drawer/Popover/Sheet/Tabs/Tooltip, and the L0 primitives.Any consumer that needs to show a metric over time therefore hand-rolls one. We
just did this for a second time in
eis-chat(rickylabs/eis-chat#237, a usage &cost surface), and ended up authoring three primitives from scratch:
AreaChartChartBlockstackedvariantRadialChartThese are ~450 lines of SVG + CSS that are not app logic. They are a design
system's job, and every consumer is paying for them again.
Why a library isn't the answer
Not asking for a recharts dependency — the opposite. Deno + Preact islands, and
the dependency policy is Deno-native /
@std→ JSR → npm only as a statedexception. A charting library is a large client bundle for what is, in the
common case, a few hundred lines of SVG driven by design tokens.
What's wanted is the same thing
fresh-uialready does well for the otherprimitives: token-driven, SSR-correct, theme-aware, no client JS unless the
interaction genuinely needs it.
What a useful primitive set looks like
From actually building these, the requirements that mattered:
literal breaks exactly one theme, silently. Intents through
data-toneagainst the semantic tokens (
--ns-primary,--ns-success, …), same as therest of
fresh-ui.area chart is pure CSS (
:hoveron transparent hit-slots revealing asibling). No hydration cost, no first-paint mismatch. Anything that needs
client JS should be opt-in, not the baseline — we hit a real production bug
where a chart region that depended on hydration painted and then vanished.
screen-reader users; the summary needs to be in
aria-label/<title>.strings. A chart primitive should not know about currencies or locales —
ours renders CHF amounts converted at display time from stored USD, which is
not something a chart could infer.
money, so "no data" has to render as a labelled empty frame, never as a zero
and never as a vanished panel. A primitive that returns
nullon an emptyseries pushes that decision onto every caller, and most will get it wrong.
cache-write tokens, which are priced differently — collapsing them into one
bar makes the chart uncostable.
Suggested scope
A minimal
@netscript/fresh-uichart set that would have covered 100% of whatwe needed:
<Chart variant="bar" | "column" | "stacked" | "line" | "area">withdata-toneintents, nice-axis ticks, and an optional CSS-only hover readout<Donut>(single ring, share of a whole) and<Radial>(concentric, serieson independent axes) — they answer different questions and shouldn't be one
component
EmptyChartframe so "no data" renders axes + an explanation rather thannothing
Happy to contribute ours upstream as a starting point if that's useful — they're
written to the
@component/@layer/@depends theme-seedcontract already.Local implementations for reference:
apps/dashboard/components/ui/area-chart.tsx,radial-chart.tsx, and thestackedvariant inchart-block.tsxinrickylabs/eis-chat#237.