DisplayReact
A countdown or a stopwatch read from the wall clock.
Rendered by React 19.3.1
Loading React…Installation
npm i @hamur/reactNot on npm yet. The package goes out under the chosen name.
Anatomy
Import the component and assemble its parts:
<Timer.Root>
<Timer.Label />
<Timer.Value />
<Timer.Trigger />
</Timer.Root>
// Not in this example: Timer.Segment, Timer.SegmentLabel, Timer.SeparatorExamples
Stopwatch
A stopwatch drawn in Segments to the hundredth; a Lap button reads the time from actionsRef, Reset clears it.
Target
A countdown to midnight UTC in hours, minutes and seconds with their SegmentLabels, and a toggle that pauses it.
Extend
A sign-out countdown that starts on mount, says "1 minute left" and can be extended by a minute.
API reference
26 props across 7 parts, in React syntax. className, style, render are listed once, under Shared props.
LabelTimer.Label
Names the Value: what the time measures. Renders a <span>.
| Data attribute | Description |
|---|---|
data-idle | Present while the timer has not started. |
data-running | Present while the timer runs. |
data-paused | Present while the timer is paused. |
data-completed | Present once the time has reached its end. |
data-overtime | Present while the time is past its end with overtime. |
data-disabled | Present when the component is disabled. |
data-pending | Present while a target countdown waits for mount to draw its time. |
RootTimer.Root
Groups all parts of the timer: a countdown or a stopwatch with its controls. Renders a <div> element, with the status region as its last child. Renders a <div>.
| Prop | Type | Default |
|---|---|---|
modeWhether the timer shows the time left or the time elapsed. | TimerMode | 'countdown' |
durationA countdown's length, or a stopwatch's limit, in milliseconds. | number | — |
targetAn instant to count down to on the wall clock; runs from mount. | TimerTarget | — |
valueThe run: the milliseconds run before startedAt, and the wall-clock start of the current | TimerValue | — |
defaultValueThe run when the timer is initially rendered. | TimerValue | { elapsed: 0, startedAt: null } |
onValueChangeEvent handler called when an action or the completion changes the run; never per tick. | ((value: TimerValue, eventDetails: TimerRootValueChangeEventDetails) => void) | — |
autoStartWhether the timer starts once after mount. | boolean | false |
intervalThe step of the shown time, in milliseconds. | number | 1000 |
largestUnitThe largest unit the time is balanced into: 'hours' shows 50:00:00 for two days and two | TimerLargestUnit | 'days' |
overtimeWhether the time keeps counting past its end. | boolean | false |
announceShown times, in milliseconds, at which the status region says the time. | readonly number[] | [] |
localeThe locale of the digits, the separators and the unit words. | Intl.LocalesArgument | — |
labelsThe texts, in English by default: the Triggers' names and the announcements. | Partial<TimerLabels> | — |
nowThe clock, read on every tick and action. | (() => number) | Date.now |
onTickEvent handler called on each change of the shown time while the page is visible. | ((time: TimerTime) => void) | — |
onCompleteEvent handler called once per run when the time reaches its end. | ((eventDetails: { reason: "complete"; event: MouseEvent | Event | PointerEvent | KeyboardEvent; } & { overdue: number; }) => void) | — |
disabledWhether the component should ignore user interaction. | boolean | false |
actionsRefA ref to imperative actions: start(), pause(), resume(), toggle(), reset(), | React.RefObject<TimerRootActions | null> | — |
| Data attribute | Description |
|---|---|
data-idle | Present while the timer has not started. |
data-running | Present while the timer runs. |
data-paused | Present while the timer is paused. |
data-completed | Present once the time has reached its end. |
data-overtime | Present while the time is past its end with overtime. |
data-disabled | Present when the component is disabled. |
data-pending | Present while a target countdown waits for mount to draw its time. |
| CSS variable | Description |
|---|---|
--timer-progress | The fraction of duration elapsed, from 0 to 1. Not set without duration. |
SegmentTimer.Segment
One unit of the time, balanced from largestUnit and padded to two digits. Place it inside the Value. Renders a <span>.
| Prop | Type | Default |
|---|---|---|
unitrequiredThe unit the segment shows. | TimerUnit | — |
| Data attribute | Description |
|---|---|
data-idle | Present while the timer has not started. |
data-running | Present while the timer runs. |
data-paused | Present while the timer is paused. |
data-completed | Present once the time has reached its end. |
data-overtime | Present while the time is past its end with overtime. |
data-disabled | Present when the component is disabled. |
data-pending | Present while a target countdown waits for mount to draw its time. |
data-unit | The unit the part shows. |
SegmentLabelTimer.SegmentLabel
The word of a unit for its value in the locale: "minute" or "minutes". Its children replace it. Renders a <span>.
| Prop | Type | Default |
|---|---|---|
unitrequiredThe unit the label names. | TimerUnit | — |
unitDisplayThe length of the word: "minutes", "min" or "m". | TimerUnitDisplay | 'long' |
| Data attribute | Description |
|---|---|
data-idle | Present while the timer has not started. |
data-running | Present while the timer runs. |
data-paused | Present while the timer is paused. |
data-completed | Present once the time has reached its end. |
data-overtime | Present while the time is past its end with overtime. |
data-disabled | Present when the component is disabled. |
data-pending | Present while a target countdown waits for mount to draw its time. |
data-unit | The unit the part shows. |
SeparatorTimer.Separator
The locale's time separator between Segments: ":" in English. Its children replace it. Renders a <span>.
| Data attribute | Description |
|---|---|
data-idle | Present while the timer has not started. |
data-running | Present while the timer runs. |
data-paused | Present while the timer is paused. |
data-completed | Present once the time has reached its end. |
data-overtime | Present while the time is past its end with overtime. |
data-disabled | Present when the component is disabled. |
data-pending | Present while a target countdown waits for mount to draw its time. |
TriggerTimer.Trigger
A button that acts on the run: toggle starts, pauses, resumes or restarts by status, and its name follows. While its action does not apply it is aria-disabled and keeps focus. Renders a <button>.
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native <button> element when replacing it | boolean | true |
actionrequiredWhat the button does: toggle starts, pauses, resumes or restarts by status. | TimerAction | — |
amountThe milliseconds an add button adds. | number | 60000 |
focusableWhenDisabledWhether the button stays focusable when disabled. | boolean | true |
| Data attribute | Description |
|---|---|
data-idle | Present while the timer has not started. |
data-running | Present while the timer runs. |
data-paused | Present while the timer is paused. |
data-completed | Present once the time has reached its end. |
data-overtime | Present while the time is past its end with overtime. |
data-disabled | Present when the component is disabled. |
data-pending | Present while a target countdown waits for mount to draw its time. |
data-action | The action the button performs. |
ValueTimer.Value
The time: a hidden long form for screen readers and the drawn text, or the Segments in its children. Its name is the Label. Renders a <time>.
| Prop | Type | Default |
|---|---|---|
childrenReplaces the drawn text, never the spoken one: Segments, or a function of the drawn text and | React.ReactNode | ((formatted: string, time: TimerTime) => React.ReactNode) | — |
| Data attribute | Description |
|---|---|
data-idle | Present while the timer has not started. |
data-running | Present while the timer runs. |
data-paused | Present while the timer is paused. |
data-completed | Present once the time has reached its end. |
data-overtime | Present while the time is past its end with overtime. |
data-disabled | Present when the component is disabled. |
data-pending | Present while a target countdown waits for mount to draw its time. |
Shared props
Every part that renders an element takes these.
| Prop | Type |
|---|---|
classNameCSS class applied to the element, or a function that
returns a class based on the component's state. | string | ((state: State) => string | undefined) |
styleStyle applied to the element, or a function that
returns a style object based on the component's state. | React.CSSProperties | ((state: State) => React.CSSProperties | undefined) |
renderAllows you to replace the component's HTML element
with a different tag, or compose it with another component. | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State> |
Keyboard and accessibility
Keyboard
Timer has no keys of its own. Each Trigger is a native button, so Enter and Space press it, and Tab moves through them in the order placed. A Trigger whose action does not apply (Reset before a start, Add after the end) stays in the Tab order and ignores the press.
Accessibility
| Part | Sets | So that |
|---|---|---|
| Value | a time with role="timer", aria-live="off", aria-atomic="true" and datetime (PT4M59S), named by the Label through aria-labelledby | the time is read on demand, under a name that never changes, and is never spoken each tick |
| Value | a visually hidden span with the long form, and the drawn text or the Segments in an aria-hidden="true" span | "4 minutes, 59 seconds" is read instead of an ambiguous "4:59" |
| Label | a span whose id names the Value | the Value says what the time measures |
| Trigger | a button of type button named by its text, or "Start", "Pause", "Resume", "Restart", "Reset" or "Add 1 minute" (labels); never aria-pressed | the name says what the press will do, and its change is the feedback for the press |
| Trigger | aria-disabled="true" while its action does not apply or the Root is disabled; it stays focusable | focus stays on the button when it stops applying under it, such as Pause at the end |
| Root | a div with no role, and a visually hidden polite, atomic status region as its last child, empty until a message | the completion and the milestones are announced once, without moving focus (WCAG 4.1.3) |
| Segment | a span with the unit's value, inside the hidden span | the digits are drawn one unit at a time and read once, in the long form |
| SegmentLabel | a span with the unit's word in the locale ("minutes", "min"), inside the hidden span | the drawn units can be labelled without being read twice |
| Separator | a span with the locale's time separator, inside the hidden span | the separator is drawn but never read |
Focus order
- Each Trigger is one Tab stop, in the order placed; the Value and the Label take no focus.
- Focus stays on a Trigger through the press, the change of its name and the end of the run.
- Nothing takes focus on mount, at a milestone or at the end.