Data Visualization
Lightweight charts built from CSS and inline SVG — no charting
library, no build step. Bars, pies, gauges and heatmaps are pure CSS
driven by custom properties; lines, areas, scatters and radars are
hand-written SVG that the stylesheet colors for you. Every series
pulls from the --b-chart-1…6 tokens, and every chart
pairs with .b-legend and .b-axis.
Chart Anatomy
.b-chart is the frame: a titled column that stacks
.b-chart__title, a .b-chart__canvas
(position-relative, so gridlines and tooltips can float inside it),
and a .b-legend. Axes sit beside or below the canvas.
Everything below is this one recipe, swapping the middle.
- Delivered
role="img" with a concise
aria-label that states the takeaway (“peaking
Thursday at 4,120”), or use <figure> +
<figcaption>. For data people need to act on,
follow the chart with a .b-sr-only (or disclosed) table
of the numbers, and mark decorative internals — gridlines, axes,
sparklines — with aria-hidden="true".Column Chart
.b-columns lays vertical bars on a flex baseline. Each
.b-columns__bar takes its height from
--v (a percentage of the chart height,
--b-chart-h, default 10rem) and its x-label from
data-label. Recolor any bar with
--b-chart-color — here Thursday's peak.
Rounded
.b-columns--rounded turns bars into capsules — the
Screen Time look. Best with generous gaps and a dozen bars or fewer.
Stacked segments
Nest an <i> inside a bar for a bottom-aligned
segment: the bar is the total, the segment its share. The segment
defaults to --b-chart-2; both accept
--b-chart-color.
- Sent
- Opened
.b-bars chart instead, which ranks and compares
precisely.Bar Chart
Horizontal bars for ranked categories with long labels. Each
.b-bars__row is a three-column grid: label, track,
value. Fill width comes from --v; widen the label
column with --b-bars-label-w when city names need room.
Line Chart
Lines are plain inline SVG — you own the coordinates, the stylesheet
owns the look. Give the polyline path class="b-series"
inside an svg.b-line-svg; add dashed
.b-svg-grid lines and .b-svg-axis-label
texts. The stroke stays 2px at any size thanks to
vector-effect: non-scaling-stroke.
Area Chart
An svg.b-area-svg with two paths: a closed
.b-area-fill underneath (same points, dropped to the
baseline) and the .b-series stroke on top. The fill is
an 18% tint of the series color, so one
--b-chart-color on the <svg> retints
both.
Pie Chart
A single div and a conic-gradient. Pass color stops
through --b-pie as
color start end triplets covering 0–100%; size it with
--b-pie-size (default 9rem). Always pair with a legend
— the wedges carry no text of their own.
- Mobile · 45%
- Desktop · 33%
- Tablet · 22%
Donut Chart
Wrap a .b-pie in .b-donut: a radial mask
punches the hole and .b-donut__center stacks on the
same grid cell for a headline stat. The hole radius is
--b-donut-hole, default 62% — raise it for a thinner
ring.
- Newsletter · 52%
- Product updates · 29%
- Digests · 19%
Scatter Plot
An svg.b-scatter-svg full of circle.b-dot
elements — you supply cx, cy and
r. Dots default to --b-chart-1; put
--b-chart-color on individual circles for a second
series. Session length against pages read, mobile vs desktop:
- Mobile
- Desktop
Data points
.b-dot is reusable on any SVG chart — drop circles onto
a line chart to mark readings. Dots wear a hairline ring of
var(--b-bg) so they cut cleanly out of gridlines, and
grow from r="4" to 6 on hover as a pointer affordance.
Actual hit-testing and value display is app-side wiring — see
Chart Tooltip.
Radar Chart
Concentric polygon.b-web rings and
line.b-web spokes form the web; the filled
polygon.b-series plots the values. Five axes on a
200×200 viewBox, center (100,100), outer radius 80.
Heatmap
A CSS grid of <i> cells. Column count comes from
--b-heatmap-cols; each cell's --v is an
intensity from 0 to 1, mixed into the series color with
color-mix() — 0 renders the empty fill, 1 the full
--b-chart-color. Twelve months of commits, one row per
week of the month:
Gauge
A half-donut for a single 0–100 reading. Set
--b-progress as a unitless number (here 68 — note the
linear progress bar takes a
percentage instead), size with --b-gauge-size, and
recolor with --b-chart-color. The label hangs below
the arc, so leave it some room.
Sparkline
A word-sized trend for stat cards and table cells:
svg.b-sparkline stretches its single path across a
2rem-tall box (preserveAspectRatio="none" lets it fill;
the stroke never distorts). Purely decorative — keep it
aria-hidden and let the stat carry the numbers.
Progress Chart
Stacked .b-progress-chart__rows, each a
__head (label left, value right) over a standard
.b-progress bar from
Feedback. Here
--b-progress is a width percentage on the fill —
96%, unit included.
Legend
A wrapping ul.b-legend of swatch + label pairs. The
.b-legend__swatch defaults to
--b-chart-1; set the others inline. The six series
tokens resolve to accent blue, teal, purple, orange, pink and green
— and re-tune automatically in dark mode.
- Push
- In-app
- SMS
- Social
- Webhooks
Axis & Gridlines
.b-axis spreads tick labels across a row in tabular
figures; .b-axis--y stacks them beside a canvas.
.b-gridlines floats dashed rules through a
position-relative canvas — one <i> per line.
Columns reserve a 1.4rem label strip at the bottom, so give the
gridlines inset-block-end: 1.4rem and the y-axis the
same bottom padding to keep “0” on the baseline.
The horizontal axis also works standalone — under heatmaps, area charts or timeline strips:
Chart Tooltip
.b-chart-tip is a floating value bubble for the nearest
data point: absolutely positioned inside the relative
.b-chart__canvas, self-centered above its anchor via
translate, with a small caret. Barua ships the look
only — tracking the pointer, picking the point and setting
left/top is app-side JavaScript. Shown
pinned over Thursday:
Rule Mark
Swift Charts' RuleMark: a dashed goal or threshold line
laid over any chart canvas. Position with bottom as a
percentage of the scale; --accent recolors it for
targets rather than limits.
Linear Gauge
SwiftUI's linear Gauge style: a graded track with a
marker at the current value (--v) and min/max labels
beneath. Use __track--plain for a neutral track.