Motion
Short, interruptible motion that confirms what happened and never makes anyone wait.
Fast in, faster out
Every transition uses one strong ease-out curve. It covers most of the distance in the first few frames, so the interface answers at once, then settles gently. Exits run shorter than entrances, because once someone is done, the thing should get out of the way.
Grow from the trigger
Menus, popovers, selects, and tooltips fade in and grow from 97% at the edge that opened them, so the eye follows where they came from. Buttons shrink a little while pressed with a pointer, and switches slide their thumb. Larger surfaces such as drawers take longer because they travel farther.
The keyboard and reduced motion
Someone moving by keyboard has already decided, so a popup opened from the keyboard appears at once, and keyboard toggles switch immediately. With reduced motion turned on, components keep their fades and drop the movement and scaling.
Build it
Press feedback lives in Buttonโs own classes, limited to pointer presses:
123// Pointer presses shrink the control slightly; keyboard activation and reduced motion skip it."motion-safe:[&:active:not(:focus-visible):not(:disabled)]:scale-[0.96]""transition-[background-color,color,scale] duration-150 ease-(--ease-out)"Popups share the same entrance and exit:
123456// Popups grow from their trigger and leave faster than they arrive."origin-(--transform-origin) transition-[opacity,scale] duration-150 ease-(--ease-out)""data-starting-style:opacity-0 data-ending-style:opacity-0 data-ending-style:duration-100""motion-safe:data-starting-style:scale-97 motion-safe:data-ending-style:scale-97"// Keyboard users get the popup at once."[:root:has(:focus-visible)_&]:transition-none"123:root { --ease-out: cubic-bezier(0.23, 1, 0.32, 1);}