Foundations
Motion
Motion exists to keep the subject — the parcel — anchored while everything else rearranges around it. Nothing decorative moves.
The grammar
- Enter slow, exit fast. Always asymmetric: entering decelerates, leaving accelerates. A thing that arrives and departs at the same speed reads as a glitch.
- Big surfaces use emphasized, small chrome uses standard. A side sheet and a dropdown moving on the same curve makes the sheet feel flighty.
- Transform and opacity only. Anything that animates width, height or a colour ramp is asking the compositor to re-layout sixty times a second, on a page that is also drawing a map.
- The verdict never animates its wording. An icon joins or leaves the approval stack instantly, so a changing answer reads as a change of state rather than a loading state.
Easing & duration
Easing
--gd-ease-standardcubic-bezier(.2, 0, 0, 1)the default, for anything that both enters and moves--gd-ease-standard-decelcubic-bezier(0, 0, 0, 1)small chrome ENTERING — pills, dropdowns, chips--gd-ease-standard-accelcubic-bezier(.3, 0, 1, 1)small chrome LEAVING--gd-ease-emphasizedcubic-bezier(.2, 0, 0, 1)big surfaces — sheets, side panels--gd-ease-emphasized-decelcubic-bezier(.05, .7, .1, 1)a big surface ENTERING--gd-ease-emphasized-accelcubic-bezier(.3, 0, .8, .15)a big surface LEAVINGDuration
--gd-dur-instant100msa state swap the eye should not track--gd-dur-fast150msTHE default for hover and press feedback--gd-dur-normal200msa popover, a chip--gd-dur-slow300msa dropdown, an accordion--gd-dur-slower400msa side sheet or bottom sheet--gd-dur-long500mslarge-surface TRAVEL — a ~600px side sheet crossing the viewport. Distance sets duration: at 400ms that much travel reads as a jump-cut rather than an arrivalReduced motion
Honoured once, globally, rather than per component — so a new animation is covered the moment it is written.
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } }0.01ms rather than 0 is deliberate: a zero-duration transition never fires transitionend, so a component waiting on it would hang forever. Setting it to zero would turn a preference into a broken dialog.