Expand description
The time axis of the make-family design system.
makeover resolves colour, makeover-geometry resolves distance, and
makeover-layout names what a thing is without resolving anything. This
crate answers the remaining question of the same shape: how long.
The move is the one the family makes everywhere. A duration is named by what it is waiting for, and the number follows:
| Moment | What it waits for | Resolves to |
|---|---|---|
Moment::Revert | a control returning to its resting label after confirming | 1500ms |
Moment::Clear | a status line emptying itself | 2000ms |
Moment::Dismiss | a transient notice’s lifetime | 3000ms |
Moment::Debounce | typing settling before the work starts | 150ms |
Whether a toast should live 3000ms or 3500ms is unanswerable on its own.
Whether a message is a toast or a banner is not. That is the whole argument
for the layer, and it is the same one Gap makes
about six pixels.
§One duration per moment, on every renderer
A moment resolves to exactly one duration everywhere, whatever the renderer and whatever the theme.
A per-renderer table, resolving the way
Density resolves gaps, is the wrong shape.
Distance has a renderer axis because a fingertip is coarser than a cursor
and a terminal cell is coarser than a pixel: the surface differs. Time
does not differ that way. A second is a second in a browser, in egui and in
a terminal, and the reader waiting it out is the same reader.
So a divergence here is a bug report.
§What is deliberately not a moment
Three classes are out of scope, and leaving them out is most of what makes the four above coherent.
A race is not a moment. Waiting 300ms before navigating because the write “should” have landed, or 150ms before closing a dropdown so a mousedown can beat the blur, is a synchronisation bug wearing a duration’s clothes. Naming those would launder them into design decisions and give every future one a token to hide behind. They get fixed per screen.
Severity is not a moment either, and it is not a fifth number. A longer
lifetime for an error toast is one statement in disguise: a message the
user must not miss does not go away on its own. makeover-layout already
has the word for it: such a message is not Notice::transient. It is a
banner, and a banner has no lifetime. See notice_lifetime.
A poll interval is not a moment. A retry backoff, a health check, an update check: those are answerable from what they talk to, not from what a reader can follow, and nothing here has an opinion about them.
§Motion is a separate axis
Moment says how long a state lasts. Motion says how long a change
takes. CSS itself draws that line, a setTimeout against a
transition-duration, and folding the two together makes a “timing scale”
unusable: 300ms of fade and 3000ms of toast answer different questions.
Motion is an enum rather than a constant so a second measured
transition has somewhere to go. It grows when something is measured.
§Cadence is a third question
Moment is how long a state lasts and Motion is how long a change
takes. Both are one-shot: something starts, it ends. Cadence is how
often a repeating mark repeats, which neither of the other two can answer
without lying about its own shape.
The crate header’s exclusion of poll intervals does not cover it, and the difference is the same one that exclusion rests on. A backoff or a health check is answerable from what it talks to. How fast a mark may blink before it reads as an alarm is answerable from the reader, which is what every rung in this crate is answerable from.
§Reduced motion
Anything whose whole existence is movement has a motion-off path, and
Cadence is that. A duration that is not animating anything is
unaffected: Moment::Debounce is a wait, and reducing motion does not
make input settle faster.
Two halves, because a browser and an egui window learn about the preference
differently. A web surface gets a prefers-reduced-motion block in the
generated stylesheet and needs no code. Every other renderer asks
activity_blink, or blink for the phase, with the
MotionPreference its platform reported, as it asks
notice_lifetime with the Persistence the description gave.
§The numbers
Each value is pinned by a test. Changing one is a design decision about every consumer at once, so the test failing is that argument happening out loud. What each is answerable to is stated on the member.
§Consumers
Web surfaces bake timing_css in at build time. Nothing here changes at
runtime, so there is no load-time JS step, exactly as with geometry. egui
and ratatui surfaces read Moment::duration instead, which is why this
is a crate rather than a stylesheet.
Enums§
- Cadence
- How often a repeating mark repeats.
- Moment
- A duration named by what it is waiting for.
- Motion
- How long a change takes, as opposed to how long a state lasts.
- Motion
Preference - Whether the reader has asked for less motion, as their platform reported it.
- Persistence
- Whether a notice goes away on its own.
Functions§
- activity_
blink - How fast the activity mark blinks, given whether the reader has asked for less motion.
- blink
- Which state the activity mark is in,
elapsedinto a wait, and how long it holds that state. - notice_
lifetime - How long a notice lives, given whether it goes away on its own.
- reduced_
motion_ css - The motion-off block, for a reader who has asked for less of it.
- timing_
css - The time axis as a stylesheet, inside the family’s cascade layer.
- timing_
css_ declarations - Emit the time axis as CSS declarations, no selector.
- timing_
css_ vars - Emit the whole time axis: a
:root { ... }block, then the motion-off block.