Skip to content
microcharts
ReferenceExpressiveHourglass

Hourglass

React hourglass chart — sand area that splits elapsed and remaining, area-true. Tiny accessible SVG microchart; RSC-safe static or /interactive.

encodes
sand area split top (remaining) / bottom (elapsed)
precision
medium
nodes
4

Hourglass answers "how much time is gone and how much remains?" — the two-sided story a progress bar can't tell. Sand fills the top chamber (remaining) and the bottom (elapsed), and both areas are area-true: a naive linear-height fill in a triangular bulb would overstate early progress by up to 2×, so the geometry solves for true proportional area. A thin stream at the neck marks "running"; it disappears at both ends, because finished and not-started are shape-distinct states.

Hourglass
interactive · 3.62 kB · static · 2.04 kB

Install

Import & usereact/hourglass
import { Hourglass } from "@microcharts/react/hourglass";<Hourglass value={0.75} title="Session" />
Needs package + stylesheetSet up with AI

Try it

When to use it

  • Good for — a deadline or session-expiry read in a sentence, a TTL cell where remaining is the story, or a time-boxed tab or countdown.
  • Avoid for — exact percentages (Progress), trends (Sparkline), or non-time fractions.

Sizing

Variants

labelled remaining + finished

Edge cases

zero — not started, no stream
finished — no stream, all sand settled
out-of-range clamps to 0–1 — visually and in the accessible name
NaN — treated as 0 elapsed (documented)

Four homes

Why this default

value is the elapsed fraction — the same polarity as Progress — so the two compose in one product without re-learning which way is which. The area-true solve is the load-bearing honesty rule: in an inherently nonlinear container, the sand areas are exactly proportional to elapsed and remaining. The stream is a binary state mark, not decoration, and is never animated in the static entry — it simply renders while 0 < value < 1.

Accessibility

The accessible name carries both sides — a glyph above reads "70% elapsed, 30% remaining." The interactive entry cross-fades the sand levels on change (opacity plus a scale-from-the-floor settle, never a path interpolation) and announces only when the value crosses a documented threshold (50 / 90 / 100%), so a streaming value never spams.

This chart is a single unit, so there is nothing to rove between: a click, tap, Enter or Space selects it and fires onSelect, and no selection stays pinned. That is the scalar half of the shared interaction contract. Hover or focus also reveals the reading itself in a floating chip, for the sizes and label modes where the mark does not print it; readout={false} drops the chip and keeps everything else.

Props

PropTypeNotes
value*numberElapsed fraction 0–1 (like Progress).
streambooleanThe running-sand cue (default true).
label"none" | "remaining" | "elapsed"Print the percent that matters to the context.
fontSizenumberType size of the percent numeral, in viewBox units (default 8).

Plus the shared grammar — data, domain, color, title, summary, format — and the layout props (width, height, className, style) that every chart accepts. Interactive entries also share animate and live, and — wherever a chart has more than one navigable unit — onActive, onSelect, selectedIndex and defaultSelectedIndex; and — wherever the chart shows a hover value — readout. See the shared grammar.

Related charts