Measure Line
A measurement overlay — two end-cap ticks, a thin line connecting them, interrupted in the middle by a labelled pill. Use it inside a teaching demo to call out the total width or height of a structure: "this array has length n = 5," "this cell is 32px tall."
row
Customize
Shape
horizontal
200px
Tone
neutral
Installation
npx shadcn@latest add https://craftbits.dev/r/measure-line.jsonUsage
import { MeasureLine } from "@craft-bits/core";
<MeasureLine direction="horizontal" length={240} label="n = 5" />Pair with the row or column it's measuring:
<div className="flex flex-col gap-2">
<YourRow />
<MeasureLine direction="horizontal" length={240} label="width" />
</div>Vertical, alongside a column:
<div className="flex gap-2">
<YourColumn />
<MeasureLine direction="vertical" length={160} label="height" />
</div>Understanding the component
- Pure SVG. The whole component is one
<svg>with four<line>end-cap/dimension strokes, one centered<rect>pill, and one<text>label. No DOM children, no layout effects. - Auto-fit label. The label pill's width is estimated from
label.length × fontSize × 0.62(a workable monospace constant). Override with thelabelWidthprop if you need pixel-exact sizing or use a proportional font. - Direction-symmetric layout. The geometry mirrors cleanly between horizontal and vertical. Picking
directionswaps the role of the two axes — thelengthis always along the measured axis and the perpendicular axis auto-fits to the pill plus a few pixels of padding. - Token-based colors. Four tones (
neutral/success/error/accent) map to--cb-*CSS variables. The SVGstroke,fill, andtextattributes consume those vars inline, so a re-theme repaints every measure-line. - No motion. A measure-line is a static caption — it doesn't enter or leave. If you need it to fade in with the demo it's measuring, wrap it in your own
<motion.div>and pass anopacityanimation; the SVG itself stays still. - SSR-safe. No
useEffect, no canvas measurement, nowindowaccess at render. Renders identically on server and client.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
direction | "horizontal" | "vertical" | required | Axis along which the line runs. |
length | number | required | Pixel length along the measured axis. |
label | string | required | Text rendered in the centered pill. Doubles as the SVG aria-label. |
color | "neutral" | "success" | "error" | "accent" | "neutral" | Tone — drives stroke / pill / text colors. |
labelWidth | number | auto | Override the auto-estimated pill width (pixels). |
className | string | — | Merged onto the rendered <svg>. |
...rest | SVGAttributes<SVGSVGElement> | — | Any other <svg> prop. |
Accessibility
- The
<svg>isrole="img"witharia-labelset from thelabelprop. Screen readers announce the measurement as a single image with that text — no need to repeat the label in surrounding markup. - Decorative end-caps and connecting strokes carry no individual ARIA — the entire image is described by the root
aria-label. - Color contrast in the default theme: stroke and text use
--cb-fg-muted(neutral) or a tone-specific equivalent (--cb-success,--cb-error,--cb-accent) — all four pass WCAG AA against--cb-bg-elevated. - No motion —
prefers-reduced-motionis automatically respected.
Credits
- Extracted from:
terminal-dreams(src/components/principles/demo-primitives/MeasureLine.tsx). The original measured label width with auseEffect-bound<canvas>measureTextcall; craft-bits swaps that for a cheaplength × fontSize × 0.62heuristic so the component is SSR-safe and ref-free. ThelabelWidthprop is the override hatch for cases where the heuristic falls short. The end-cap / line / pill / text geometry survives unchanged.