Components
All 23, in the framework you pick above. The props, types and code samples change with it; the preview does not — see the note under each title.
Actions
Button
HTML / CSS · .hg-btn
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| variant | "primary" | "ghost" | "icon" | Defaults to primary. |
| size | "sm" | Omit for the default height. |
| block | boolean attribute | Fills the width of its container. |
| loading | boolean attribute | Prepends the pulsing dot and sets aria-busy. |
| disabled | boolean attribute | |
| href | string | Renders an anchor instead of a button. |
| aria-label | string | Required on an icon button; copied onto the inner control. |
Usage — HTML / CSS
<button class="hg-btn hg-btn--primary">Start a task</button>
<button class="hg-btn hg-btn--ghost hg-btn--sm">Cancel</button>
<!-- An icon button is nothing but an icon, so it must carry the label -->
<button
class="hg-btn hg-btn--icon"
aria-label="More options"
>
<span aria-hidden="true">⋯</span>
</button>
TextLinkArrow
HTML / CSS · .hg-link
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| direction | "back" | Omit for forward. |
| href | string | Renders an anchor. Without it you get a button. |
Usage — HTML / CSS
<a
class="hg-link"
href="/clients"
>
View all <span class="hg-link__arrow" aria-hidden="true">→</span>
</a>
<button class="hg-link hg-link--back">
<span
class="hg-link__arrow"
aria-hidden="true"
>
→</span> Back
</button>
Forms
TextField
HTML / CSS · .hg-field
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| value | string | What is shown. Typing fires hg-change and changes nothing. |
| label hint error | string | As above. |
| mono | boolean attribute | |
| multiline | boolean attribute | With rows. |
| disabled | boolean attribute | |
| auto | boolean attribute | Lets the field keep its own value. Prototypes only. |
| hg-change | event | detail.value is the new text. |
Usage — HTML / CSS
<label class="hg-field">
<span class="hg-field__label">Description</span>
<input
class="hg-field__input"
placeholder="What are you working on?"
/>
<span class="hg-field__hint">One line. Keep it short.</span>
</label>
<!-- Invalid: weight and an icon, never a red border -->
<div class="hg-field hg-field--invalid">
<span class="hg-field__label">Client</span>
<input
class="hg-field__input"
aria-invalid="true"
/>
<span class="hg-field__error"><span aria-hidden="true">!</span> Pick a client.</span>
</div>
Select
HTML / CSS · .hg-select
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| value | string | |
| options | JSON string | Attributes are strings, so the list comes in as JSON. |
| label hint error | string | |
| hg-change | event | detail.value is the chosen value. |
Types
SelectOption
// Attributes are strings, so the list arrives as JSON.
options='[
{ "value": "redesign", "label": "Redesign" },
{ "value": "maintenance", "label": "Maintenance", "disabled": true }
]'
| Field | Type | Notes |
|---|---|---|
| value | string | What is stored, and what comes back from onChange. |
| label | string | What is shown. The component never translates or formats it. |
| disabled | boolean? | Rendered but not selectable. |
Usage — HTML / CSS
<label class="hg-field">
<span class="hg-field__label">Project</span>
<span class="hg-select">
<select class="hg-select__control">
<option value="redesign">Redesign</option>
<option value="maintenance">Maintenance</option>
</select>
<span
class="hg-select__chevron"
aria-hidden="true"
>
▾</span>
</span>
</label>
DateTimeField
HTML / CSS · .hg-dtf
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| value | ISO string | hg-change reports a real Date in detail.value. |
| min max | ISO string | |
| week-starts-on | 0 | 1 | |
| locale | string | |
| show-exact | "false" | Hides the typed box. |
| auto | boolean attribute | Lets the field keep its own value. |
Usage — HTML / CSS
<!-- The markup is the styling; the dates are always your data.
Build the cells yourself, or use one of the framework libraries. -->
<div class="hg-dtf">
<div class="hg-dtf__head">
<button
class="hg-btn hg-btn--icon"
aria-label="Previous month"
>
‹</button>
<span class="hg-dtf__month">August 2026</span>
<button
class="hg-btn hg-btn--icon"
aria-label="Next month"
>
›</button>
</div>
<div
class="hg-dtf__grid"
role="grid"
>
<!-- .hg-dtf__dow ×7, then .hg-dtf__day ×42 --></div>
<div class="hg-dtf__time">
<div
class="hg-dtf__col"
role="listbox"
aria-label="Hour"
>
<!-- .hg-dtf__opt ×24 --></div>
<div
class="hg-dtf__col"
role="listbox"
aria-label="Minute"
>
<!-- .hg-dtf__opt ×60 --></div>
</div>
</div>
Controls
Toggle
HTML / CSS · .hg-toggle
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| checked | boolean attribute | |
| label | string | |
| disabled | boolean attribute | |
| auto | boolean attribute | Lets the toggle flip itself. Prototypes only. |
| hg-change | event | detail.checked. |
Usage — HTML / CSS
<button
class="hg-toggle"
role="switch"
aria-checked="true"
id="billable"
>
<span class="hg-toggle__track"><span class="hg-toggle__knob"></span></span>
<span class="hg-toggle__label">Billable</span>
</button>
<script>
const el = document.getElementById("billable");
el.addEventListener("hg:change", (e) => {
save(e.detail.checked);
Hourglass.setToggle(el, e.detail.checked);
});
</script>
Segmented
HTML / CSS · .hg-segmented
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| options | JSON string | [{"value":"day","label":"Day"}]. |
| value | string | |
| block | boolean attribute | |
| auto | boolean attribute | |
| hg-change | event | detail.value. |
| layout() | method | Re-measures the thumb. Called for you on resize. |
Types
SegmentedOption
React types label as a node, so an icon or a mono figure can sit in a segment. Vue, Svelte, Angular and React Native take a plain string — a segment is a one-word label, and anything richer belongs in a different control.
// Attributes are strings, so the list arrives as JSON.
options='[
{ "value": "day", "label": "Day" },
{ "value": "week", "label": "Week" },
{ "value": "month", "label": "Month" }
]'
| Field | Type | Notes |
|---|---|---|
| value | string | Generic, so a union type ("day" | "week") narrows onChange for you. |
| label | ReactNode | string | What is shown. Never formatted by the component. |
Usage — HTML / CSS
<div class="hg-segmented">
<span class="hg-segmented__thumb"></span>
<button
class="hg-segmented__item"
data-value="day"
aria-selected="true"
>
Day</button>
<button
class="hg-segmented__item"
data-value="week"
aria-selected="false"
>
Week</button>
</div>
<script>
Hourglass.enhance();
el.addEventListener("hg:change", (e) => Hourglass.setSegmented(el, e.detail.value));
</script>
ChoiceRow
HTML / CSS · .hg-choice
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| kind | "radio" | "check" | Defaults to check. |
| checked | boolean attribute | |
| title description | string | |
| disabled | boolean attribute | |
| auto | boolean attribute | |
| hg-change | event | detail.checked and detail.value. |
Usage — HTML / CSS
<button
class="hg-choice hg-choice--radio"
role="radio"
aria-checked="true"
>
<span class="hg-choice__mark"><span aria-hidden="true">●</span></span>
<span class="hg-choice__body">
<span class="hg-choice__title">Round to the nearest minute</span>
<span class="hg-choice__desc">Exports keep the raw seconds.</span>
</span>
</button>
FilterPill
HTML / CSS · .hg-pill
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| pressed | boolean attribute | |
| count | string | |
| value | string | Echoed back in the event, so one listener can serve a row of pills. |
| auto | boolean attribute | |
| hg-change | event | detail.pressed, detail.value. |
Usage — HTML / CSS
<button
class="hg-pill"
aria-pressed="true"
>
All <span class="hg-pill__count">42</span></button>
<button
class="hg-pill"
aria-pressed="false"
>
Billable <span class="hg-pill__count">31</span></button>
Content
Card
HTML / CSS · .hg-card
Preview
Acme Ltd
3 projects · last worked yesterday
Floating
For something genuinely above the page.
Props
| Prop | Type | Notes |
|---|---|---|
| bordered flat floating | boolean attribute | |
| href | string | Renders an anchor. |
Usage — HTML / CSS
<div class="hg-card hg-card--bordered">
<p class="hg-h3">Acme Ltd</p>
<p class="hg-small hg-muted">3 projects · last worked yesterday</p>
</div>
Row
HTML / CSS · .hg-row
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| title meta value | string | |
| href | string | |
| interactive | boolean attribute | Hover tint and pointer without a link. |
| — | Children are projected after meta, inside the body. |
Usage — HTML / CSS
<button class="hg-row hg-row--interactive">
<span
class="hg-row__lead"
aria-hidden="true"
>
▸</span>
<span class="hg-row__body">
<span class="hg-row__title">Rebuild the timesheet flow</span>
<span class="hg-row__meta">Acme · Redesign</span>
</span>
<span class="hg-row__value">2:15</span>
</button>
SectionLabel
HTML / CSS · .hg-section-label
Preview
Tracked today6:12
Recent clients
Props
| Prop | Type | Notes |
|---|---|---|
| aside | string | Quiet text on the right. |
Usage — HTML / CSS
<p class="hg-section-label">
<span>Tracked today</span>
<span class="hg-section-label__aside">6:12</span>
</p>
StatTile
HTML / CSS · .hg-stat
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| label value aside | string | Children render inside the value, after value. |
| compact | boolean attribute |
Usage — HTML / CSS
<div class="hg-stat">
<span class="hg-stat__label">Tracked</span>
<span class="hg-stat__value">32:10</span>
<span class="hg-stat__aside">This week</span>
</div>
Chip
HTML / CSS · .hg-chip
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| removable | boolean attribute | Shows the remove control. |
| remove-label | string | |
| hg-remove | event |
Usage — HTML / CSS
<span class="hg-chip">
Acme Ltd
<button
class="hg-chip__remove"
aria-label="Remove Acme Ltd"
>
✕</button>
</span>
Badge
HTML / CSS · .hg-badge
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| variant | "solid" | "outline" | "lock" | Omit for the default. |
Usage — HTML / CSS
<span class="hg-badge">Draft</span>
<span class="hg-badge hg-badge--solid">Running</span>
<span class="hg-badge hg-badge--lock"><span aria-hidden="true">🔒</span> Pro</span>
EmptyState
HTML / CSS · .hg-empty
Preview
Nothing tracked yet
Entries you record will show up here, newest first.
Props
| Prop | Type | Notes |
|---|---|---|
| icon | string | A glyph. |
| title | string | |
| action-label | string | Renders the dashed CTA. |
| hg-action | event |
Types
EmptyStateAction
One action, or none. An empty state offering two ways out is a menu, and the dashed CTA is the only affordance the component draws.
<!-- No object: the label is an attribute and the press is an event. -->
<hg-empty-state title="Nothing tracked yet" action-label="Start a task"></hg-empty-state>
<script type="module">
el.addEventListener("hg-action", start);
</script>
| Field | Type | Notes |
|---|---|---|
| action-label | attribute | Renders the dashed CTA. Omit it and no button appears. |
| hg-action | event | Fired on press. Bubbling and composed. |
Usage — HTML / CSS
<div class="hg-empty">
<span
class="hg-empty__icon"
aria-hidden="true"
>
◷</span>
<p class="hg-empty__title">Nothing tracked yet</p>
<p class="hg-empty__body">Entries you record will show up here, newest first.</p>
<button class="hg-empty__cta">Start a task</button>
</div>
Data
RollingNumber
HTML / CSS · .hg-reel
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| value | string | Already formatted. |
| static | boolean attribute | No travel. |
| duration delay stagger | number | Milliseconds. |
| easing | string | A CSS timing function. |
| label | string | Falls back to the value if omitted. |
Usage — HTML / CSS
<span
class="hg-h1"
data-hg-reel="4:40"
aria-label="4 hours 40 minutes"
></span>
<script>
Hourglass.enhance();
// Later — only the digits that changed will move
Hourglass.reel(el, "5:02", { label: "5 hours 2 minutes" });
</script>
ProgressBar
HTML / CSS · .hg-progress
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| value max | number | |
| label value-label | string | |
| indeterminate | boolean attribute | |
| Hourglass.setProgress(el, f) | method | HTML library only — sets the fill from a 0–1 fraction. |
Usage — HTML / CSS
<div
class="hg-progress"
id="today"
>
<div class="hg-progress__head"><span>Today</span><span>6:12 / 8:00</span></div>
<div class="hg-progress__track"><div class="hg-progress__fill"></div></div>
</div>
<script>Hourglass.setProgress(document.getElementById("today"), 6.2 / 8);</script>
Bars
HTML / CSS · .hg-bars
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| items | JSON string | Same shape as above. |
| max, label-width, value-width, stagger | number | |
| interactive | boolean attribute | Enables hg-select. |
| hg-select | event | detail.key. |
Types
BarItem
value is the number the bar is drawn from; valueLabel is the text printed beside it. They are separate because the bar needs a magnitude and the reader needs 7:24 — and only you know that 7.4 hours is written that way.
// Attributes are strings, so the list arrives as JSON.
items='[
{ "key": "2026-08-16", "label": "16", "value": 7.4, "valueLabel": "7:24", "emphasis": true },
{ "key": "2026-08-17", "label": "17", "value": 3.7, "valueLabel": "3:40" }
]'
| Field | Type | Notes |
|---|---|---|
| key | string | Identity. Used for the render key and handed back by onSelect. Never rendered. |
| label | ReactNode | string | The left column. The component never formats a date or a name. |
| value | number | What the bar length is derived from, against max. |
| valueLabel | ReactNode? | The right column. Falls back to value if omitted, which is rarely what you want. |
| emphasis | boolean? | Accent fill and full-weight text — the "today" bar. Tone and weight, never a second hue. |
Usage — HTML / CSS
<div
class="hg-bars"
id="week"
>
<div class="hg-bars__row">
<span class="hg-bars__label">16</span>
<span class="hg-bars__track"><span class="hg-bars__fill"></span></span>
<span class="hg-bars__value">7:24</span>
</div>
</div>
<script>
document.querySelectorAll("#week .hg-bars__row")
.forEach((row, i) => Hourglass.setBar(row, fractions[i]));
</script>
RadialGauge
HTML / CSS · .hg-gauge
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| value max size thickness | number | |
| label | string | |
| Hourglass.gauge(el, opts) | method | HTML library only — builds and updates the SVG. |
Usage — HTML / CSS
<div
class="hg-gauge"
id="dial"
>
<div class="hg-gauge__center">
<span class="hg-caption hg-muted">Today</span>
<span
class="hg-h1 hg-mono"
data-hg-reel="6:12"
aria-label="6 hours 12 minutes"
></span>
<span class="hg-micro hg-faint">of 8:00</span>
</div>
</div>
<script>
Hourglass.gauge(document.getElementById("dial"), {
value: 6.2, max: 8, size: 220, thickness: 14,
label: "6 hours 12 minutes of an 8 hour target",
});
</script>
Overlay
BottomSheet
HTML / CSS · .hg-sheet
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| open | boolean attribute | |
| title labelled-by | string | |
| auto | boolean attribute | Lets the sheet close itself. |
| hg-dismiss | event | detail.reason is scrim, close or escape. |
| [data-hg-open="id"] | attribute | On any button — opens that sheet. |
Usage — HTML / CSS
<button
class="hg-btn hg-btn--ghost"
data-hg-open="sheet"
>
Open sheet</button>
<div
class="hg-sheet"
id="sheet"
hidden
data-hg-auto
>
<button
class="hg-sheet__scrim"
aria-label="Dismiss"
></button>
<div
class="hg-sheet__panel"
tabindex="-1"
aria-labelledby="sheet-title"
>
<div class="hg-sheet__head">
<h2
class="hg-sheet__title"
id="sheet-title"
>
Start</h2>
<button
class="hg-btn hg-btn--icon"
data-hg-close
aria-label="Close"
>
✕</button>
</div>
<div class="hg-sheet__body">…</div>
</div>
</div>
Toast
HTML / CSS · .hg-toast
Preview
Props
| Prop | Type | Notes |
|---|---|---|
| message action-label | string | |
| leaving | boolean attribute | |
| hg-action | event | |
| Hourglass.toast(opts) | method | HTML library only. Owns its own timer; returns { dismiss }. |
Types
ToastAction
At most one, and it is the caller's job to dismiss the toast afterwards — the component has no timer to cancel. A toast with two decisions in it is a dialog.
<!-- No object: the label is an attribute and the press is an event. -->
<hg-toast message="Entry saved" action-label="Undo"></hg-toast>
<script type="module">
el.addEventListener("hg-action", undo);
</script>
<!-- The HTML library's imperative helper is the one place a timer is owned -->
<script>
Hourglass.toast({
message: "Entry saved",
action: { label: "Undo", onPress: undo },
durationMs: 4000,
});
</script>
| Field | Type | Notes |
|---|---|---|
| action-label | attribute | Renders the action. Omit it for a toast that only reports. |
| hg-action | event | Fired on press. |
| durationMs | number | Hourglass.toast() only. Defaults to 4000, and returns { dismiss }. |
Usage — HTML / CSS
<script>
Hourglass.toast({
message: "Entry saved",
action: { label: "Undo", onPress: undo },
durationMs: 4000,
});
</script>
ToastRegion
HTML / CSS · .hg-toast-region
Preview
A fixed region at the bottom of the viewport — see the Toast section's button for it in use.
Props
| Prop | Type | Notes |
|---|---|---|
| — | No attributes. Put <hg-toast> elements inside it. |
Usage — HTML / CSS
<!-- Created for you by Hourglass.toast() if it does not exist -->
<div
class="hg-toast-region"
role="status"
aria-live="polite"
></div>
Start
No grip and no top border. Escape, the scrim and the close button all dismiss it.