motion system
Motion Taste System
Good motion should explain state change without making the interface feel busy.
slow confirmationfast release
the starting point
The starting point is a control that needs confirmation. If the action happens instantly, it feels risky. If it opens a modal, it feels heavier than the task.
setting up the structure
The button carries two states at once: the normal surface and the confirming surface. The confirming layer is revealed inside the control, so progress stays attached to the user's finger.
That keeps the component small. No dialog, no extra screen, just a visible hold state where the action already lives.
polishing it up
The hold can be slow because it represents intent. The release should be quick because cancellation should feel effortless.
A small press scale gives immediate feedback, so the slower confirmation still feels responsive.
video controls as proof surface
The player chrome should appear only when it helps someone inspect the demo. The video stays the proof; the controls sit as a thin translucent layer over moving media.
Glass works here because it improves legibility over changing frames. If it becomes a permanent visual effect, it starts competing with the work.
## Easing Decision Flowchart
Is the element entering or exiting the viewport?
├── Yes -> ease-out
└── No
├── Is it moving or morphing on screen?
│ ├── Yes -> ease-in-out
│ └── No
│ ├── Is it a hover change?
│ │ ├── Yes -> ease
│ │ └── No
│ │
│ └── Is it constant motion?
│ ├── Yes -> linear
│ └── No -> ease-outChoosing motion from a rule keeps the interface quiet instead of leaving every easing to taste.
rules I keep
press 160ms instant tactile feedback
hold 1200ms sustained intent, linear progress
release 120-180ms fast cancellation so it never feels stuck
video chrome hover/focus only mobile stays visible because hover does not exist
final code
.hold-action {
transition: transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
}
.hold-action[data-state="holding"] {
transform: scale(0.985);
}
.hold-action__overlay {
clip-path: inset(0 100% 0 0);
transition: clip-path 180ms cubic-bezier(0.22, 1, 0.36, 1);
}
.hold-action[data-state="holding"] .hold-action__overlay {
clip-path: inset(0 0 0 0);
transition: clip-path 1200ms linear;
}