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
width = 200
Customize
Shape
horizontal
200px
Tone
neutral

Installation

npx shadcn@latest add https://craftbits.dev/r/measure-line.json

Usage

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

  1. 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.
  2. Auto-fit label. The label pill's width is estimated from label.length × fontSize × 0.62 (a workable monospace constant). Override with the labelWidth prop if you need pixel-exact sizing or use a proportional font.
  3. Direction-symmetric layout. The geometry mirrors cleanly between horizontal and vertical. Picking direction swaps the role of the two axes — the length is always along the measured axis and the perpendicular axis auto-fits to the pill plus a few pixels of padding.
  4. Token-based colors. Four tones (neutral / success / error / accent) map to --cb-* CSS variables. The SVG stroke, fill, and text attributes consume those vars inline, so a re-theme repaints every measure-line.
  5. 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 an opacity animation; the SVG itself stays still.
  6. SSR-safe. No useEffect, no canvas measurement, no window access at render. Renders identically on server and client.

Props

PropTypeDefaultDescription
direction"horizontal" | "vertical"requiredAxis along which the line runs.
lengthnumberrequiredPixel length along the measured axis.
labelstringrequiredText rendered in the centered pill. Doubles as the SVG aria-label.
color"neutral" | "success" | "error" | "accent""neutral"Tone — drives stroke / pill / text colors.
labelWidthnumberautoOverride the auto-estimated pill width (pixels).
classNamestringMerged onto the rendered <svg>.
...restSVGAttributes<SVGSVGElement>Any other <svg> prop.

Accessibility

  • The <svg> is role="img" with aria-label set from the label prop. 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-motion is automatically respected.

Credits

  • Extracted from: terminal-dreams (src/components/principles/demo-primitives/MeasureLine.tsx). The original measured label width with a useEffect-bound <canvas> measureText call; craft-bits swaps that for a cheap length × fontSize × 0.62 heuristic so the component is SSR-safe and ref-free. The labelWidth prop is the override hatch for cases where the heuristic falls short. The end-cap / line / pill / text geometry survives unchanged.