Motion

Shared motion policy, runtime, recipes, and hooks for Lattice UI.

On this page

@lattice-ui/motion is the shared place for animation behavior in Lattice UI. When a primitive needs transition timing, reduced-motion handling, enter/exit state, or interpolation, it should use the motion package instead of owning a private tween model.

How to apply it

Use System for app-wide theme and density, Layer for overlay mounting and stacking, Focus for trapped scopes and restoration, and Motion for animated state. Keeping those ownership lines clear prevents Dialog, Popover, Select, Tooltip, and Toast from each inventing different rules for the same lifecycle.

  • Use presence motion when content appears and disappears, especially portal content and indicators with exit transitions.
  • Use response motion when a mounted control settles between states, such as a Slider thumb, Switch thumb, or selected item background.
  • Use feedback motion for brief press or focus accents.
  • Put reduced-motion policy behind MotionProvider so components can respect one shared policy.

For layered components, animate a visual wrapper rather than the placement owner. Popover, Select, and Tooltip still need placement to come from Popper; Motion should make the content feel responsive without invalidating the anchor geometry.

Consumer guidance

For Dialog, keep overlay fade and panel reveal as separate motion concerns. The dialog owns open state, dismissal, focus trapping, and focus restoration; Motion owns the visual transition while those semantics stay active.

For Popover, Select, and Tooltip, let the content remain mounted while exit motion runs. Do not unmount portal content immediately on close if the user expects an exit transition.

For Toast, keep queue timing and visibility in the toast provider, then use Motion for the surface response. For Slider and Switch, let the component package own state and let Motion settle the moving part instead of deriving animation from raw pointer or toggle events.

Stable direction
@lattice-ui/motion

Part of the stable direction toward v1.0.

What It Is For

Use @lattice-ui/motion when animation behavior needs to be shared across primitives instead of recreated inside each component.

It owns motion policy, presence transitions, response motion, feedback effects, interpolation, and the scheduler that applies those changes to Roblox instances.

The package is part of the Lattice UI foundation layer with core, focus, layer, style, and system.

TSX Example Preview

Preview the first lines below, or expand to inspect the full source file.

motion.tsx
TSX Preview
import {
	createToggleResponseRecipe,
	MotionProvider,
	useResponseMotion,
} from "@lattice-ui/motion";
import React from "@rbxts/react";
...

Install

Global CLI command: lattice add motion

Monorepo local script

Use your package manager wrapper when running the local lattice command.

pnpm lattice add motion

npm package: @lattice-ui/motion

Peer dependencies

  • @rbxts/react
  • @rbxts/react-roblox

Registry notes

  • MotionProvider centralizes full-motion and reduced-motion policy.

Public Exports

  • MotionProvider
  • useMotionPolicy
  • createMotionTargetContract
  • motionTargets
  • useFeedbackEffect
  • usePresenceMotionController
  • usePresenceMotion
  • useResponseMotion
  • createPressFeedbackEffect
  • createFocusAccentEffect
  • createSurfaceRevealRecipe
  • createCanvasGroupRevealRecipe
  • createOverlayFadeRecipe
  • createPopperEntranceRecipe
  • createCanvasGroupPopperEntranceRecipe
  • createIndicatorRevealRecipe
  • createIndicatorSettleRecipe
  • createSliderThumbResponseRecipe
  • createToggleResponseRecipe
  • createSelectionResponseRecipe
  • createFieldResponseRecipe
  • createProgressResponseRecipe
  • createToastResponseRecipe
  • validateMotionProperty
  • readMotionProperty
  • writeMotionProperty
  • applyMotionProperties
  • areMotionValuesEqual
  • canInterpolateMotionValue
  • interpolateMotionValue
  • isMotionValueSettled
  • createPlacementOffset

State Model

  • MotionProvider owns the app-level motion policy. Use it when a screen needs full motion or reduced motion through mode="none".
  • Presence motion coordinates mounted, visible, exiting, and exited phases so overlays and indicators can finish exit work before disappearing.
  • Response motion settles a control toward active or inactive state, such as selected menu items, slider thumbs, switch thumbs, and progress ranges.
  • Feedback motion applies short-lived accent and recovery effects for press, focus, or interaction confirmation.
  • Motion target contracts define whether motion owns appearance, an offset wrapper, a size wrapper, layout, or an explicit custom property set.

Key API

MotionProvider

Set app or subtree policy with mode="full" or mode="none" so reduced-motion behavior is centralized instead of scattered through components.

usePresenceMotionController

Use this for enter and exit phases when content must stay mounted until motion completes.

useResponseMotion

Use this when a persistent control needs to settle between active and inactive visual states.

useFeedbackEffect

Use this for transient press or focus effects that should accent and recover without creating local tween code.

motionTargets

Declare which properties motion owns so layout, placement, and visual styling do not fight each other.

Composition Patterns

Presence for mounted surfaces

Use presence motion for Dialog, Popover, Select, Tooltip, Menu, Combobox, and indicator content that needs exit transitions.

Response for controls

Use response motion for Slider, Switch, Progress, selected items, and other controls that move or settle while staying mounted.

Feedback for short interaction effects

Use feedback motion for press and focus accents when the effect should be brief and policy-aware.

Package-owned animation behavior

Extend motion through recipes, targets, and hooks in this package instead of adding one-off tweens inside primitives.

Cautions / Limits

  • motion does not replace layer: layer owns portals, stacking, dismissal, and basic presence mounting; motion owns how animated state is applied while content is mounted.
  • motion does not replace focus: focus trapping, restoration, and keyboard flow still belong in focus and the consuming primitive.
  • Do not let motion own placement geometry unless you deliberately use a layout target; anchored overlays should let popper compute placement and let motion animate an isolated wrapper.
  • Loom preview is useful for iteration, but final timing, focus, and input behavior still need Roblox runtime verification.