UtherealUI

Motion

Spring recipes, timing curves, travel, and accessible overrides.

Choose a preset for a coherent starting point. Use motionOptions to tune individual transitions without replacing the component's interaction behavior. Motion remains a package dependency; the library owns the typed configuration and how it applies to the menu.

Explore theme and motion together in the Design Playground.

Presets

PresetPopupPointer highlightLabel / chevron
noneImmediateImmediateImmediate
subtle120 ms tween, scale 0.98, travel 4 px90 ms120 / 140 ms
expressiveSpring: stiffness 420, damping 26, mass 1; scale 0.94, travel 4 px180 ms120 / 140 ms

motion="none" disables animation even when overrides are present. Preset changes keep explicit overrides; remove motionOptions to return to the new preset's defaults.

Springs

Shared spring recipes are exported as motionTokens.spring. Each is a valid motionOptions.transition value.

RecipeStiffnessDampingMassUse
snappy520381Small, responsive changes
smooth340381Restrained panel motion
morph420261A more elastic change; the expressive default
gentle220301Slower, calmer movement
import { DropdownMenu, motionTokens } from '@uthereal/ui/dropdown-menu'

;<DropdownMenu
  items={items}
  motionOptions={{ transition: motionTokens.spring.smooth }}
/>

For your own spring, provide type: 'spring' and any of stiffness, damping, or mass. Unspecified spring fields use morph defaults. A spring is physics-based; it does not accept a tween duration or easing curve.

Easing and duration

All public duration options use milliseconds. Motion itself expects seconds for tweens; the component converts at the rendering boundary.

motionTokens.duration provides fast: 120, standard: 240, and considered: 480. motionTokens.easing provides standard: 'easeInOut', enter: 'easeOut', and exit: 'easeIn'. You can also choose linear.

<DropdownMenu
  items={items}
  motionOptions={{
    transition: {
      type: 'tween',
      duration: motionTokens.duration.standard,
      ease: motionTokens.easing.enter,
    },
    enter: { opacity: 0, scale: 0.97, offset: 8 },
    exit: { opacity: 0, scale: 0.99, offset: 3 },
    highlightDuration: 100,
    labelDuration: 120,
    chevronDuration: 180,
  }}
/>

The popup settles at opacity 1, scale 1, and zero travel. Enter/exit offsets are positive distances toward the trigger, on the axis of the actual placement. Submenus and collision-flipped menus therefore travel in the appropriate direction. transition controls both entrance and exit; there is no looping or delay option.

Option reference

Generated from the schema. Defaults below use subtle; spring-only fields show the fallback spring values. Numeric geometry uses pixels, rotation uses degrees, opacity and scale are unitless. A zero duration or offset is preserved.

OptionDefaultValues / purpose
transition.typetweentween / spring
Popup transition kind
transition.duration1200–2000
Milliseconds; zero is immediate
transition.easeeaseOutlinear / easeIn / easeOut / easeInOut
Timing curve
transition.stiffness4201–1000
Spring stiffness
transition.damping261–100
Resistance to oscillation
transition.mass10.1–10
Simulated mass
enter.opacity00–1
Starting or ending opacity
enter.scale0.980.5–1.2
Starting or ending scale
enter.offset40–40
Travel toward the trigger, px; follows actual placement
exit.opacity00–1
Starting or ending opacity
exit.scale0.980.5–1.2
Starting or ending scale
exit.offset20–40
Travel toward the trigger, px; follows actual placement
highlightDuration900–2000
Pointer highlight travel, ms; keyboard is always immediate
labelDuration1200–2000
Trigger text transition, ms
labelOffset30–24
Trigger text travel, px
chevronDuration1400–2000
Chevron rotation, ms
chevronRotation180-360–360
Open chevron angle, degrees
chevronEaseeaseInOutlinear / easeIn / easeOut / easeInOut
Chevron timing curve

Reduced motion and input

The component watches the device's prefers-reduced-motion setting, including changes while mounted. Reduced motion removes popup travel, label transitions, pointer-highlight movement, and chevron animation, regardless of the preset or supplied overrides. Focus, state changes, and action feedback remain available.

Keyboard navigation always updates its highlight immediately. Timing overrides apply to pointer movement; repeated arrow presses never wait for an animation. Avoid using motion alone to communicate a result.

Interruptions and live edits

Motion handles interrupted transitions. Closing makes the exiting popup inert immediately; an animation never delays an action or leaves a closing menu interactive. Changing design or motion arguments preserves a mounted menu's selections and IDs. Changing entrance settings is easiest to compare by reopening the menu.

Use a DropdownPreset to share motion and motionOptions across an application. Nested fields merge; changing transition.type replaces its old variant, so tween settings cannot leak into a spring. Agents use the same options and validation rules; see Building with agents.

On this page