Foundation
Motion
Motion is a tokenized interaction contract for hover, press, focus, overlays, disclosure, tabs, toasts, loading, feedback, and reduced-motion behavior.
Duration tokens
| Name | Token | Value | Use |
|---|---|---|---|
| Instant | --n-duration-instant | 80ms | Reduced-motion and technical synchronization. |
| Fast | --n-duration-fast | 140ms | Press, focus, and compact acknowledgement. |
| Normal | --n-duration-normal | 220ms | Hover, reveals, overlays, and feedback. |
| Slow | --n-duration-slow | 360ms | Page-level reveals used sparingly. |
Easing tokens
| Name | Token | Value | Use |
|---|---|---|---|
| Standard | --n-easing-standard | cubic-bezier(0.2, 0, 0, 1) | Default state changes. |
| Enter | --n-easing-enter | cubic-bezier(0, 0, 0.2, 1) | Content appearing. |
| Exit | --n-easing-exit | cubic-bezier(0.4, 0, 1, 1) | Content leaving. |
| Expressive | --n-easing-expressive | cubic-bezier(0.16, 1, 0.3, 1) | Success feedback and larger reveals. |
Semantic motion
Semantic motion variables describe intent and point to duration and easing tokens. Component source should use these aliases instead of repeating raw timing values. Hover feedback always transitions with the semantic hover pair; instant timing is not used for ordinary pointer interaction.
| Token |
|---|
--n-motion-hover-duration |
--n-motion-press-duration |
--n-motion-focus-duration |
--n-motion-reveal-duration |
--n-motion-collapse-duration |
--n-motion-overlay-enter-duration |
--n-motion-overlay-exit-duration |
--n-motion-page-enter-duration |
--n-motion-data-refresh-duration |
--n-motion-success-feedback-duration |
--n-motion-error-feedback-duration |
Tailwind motion recipes
motionClasses exposes complete, statically detectable Tailwind class strings. The former n-motion-* visual utility classes are not part of the post-migration contract.
| Name | Token | Authoring | Use |
|---|---|---|---|
| Interactive control | motionClasses.interactive | Static Tailwind recipe | Composable hover, press, and focus transitions for actions. |
| Form control | motionClasses.control | Static Tailwind recipe | Composable hover and focus transitions without press scaling. |
| Hover | motionClasses.hover | Static Tailwind recipe | Background, border, color, and opacity transitions. |
| Press | motionClasses.press | Static Tailwind recipe | Subtle press scale with reduced-motion fallback. |
| Focus | motionClasses.focus | Static Tailwind recipe | Focus ring and border transitions. |
| Overlay enter | motionClasses.overlayEnter | Static Tailwind recipe | Opacity plus small translate/scale. |
| Disclosure | motionClasses.disclosure | Static Tailwind recipe | Height and opacity for open/close content. |
| Tab indicator | motionClasses.tabIndicator | Static Tailwind recipe | Active indicator movement. |
| Skeleton | motionClasses.skeleton | Static Tailwind recipe | Calm loading pulse with reduced-motion fallback. |
Optional Motion adapter
Install motion only when a composition needs presence, interruption, coordinated sequencing, gestures, or layout continuity that would make CSS state orchestration fragile. Nerio Core never imports Motion, and unrelated adapter subpaths do not resolve the optional peer.
pnpm add motion @nerio-ui/adapters"use client";import { NerioMotionConfig, motionTransitions, motionVariants, useNerioReducedMotion,} from "@nerio-ui/adapters/motion";import { AnimatePresence, LazyMotion, domAnimation } from "motion/react";import * as m from "motion/react-m";export function Presence({ visible }: { visible: boolean }) { return ( <NerioMotionConfig> <LazyMotion features={domAnimation} strict> <AnimatePresence initial={false}> {visible ? ( <m.div key="panel" initial="hidden" animate="visible" exit="exit" variants={motionVariants.fadeScale} transition={motionTransitions.normal} /> ) : null} </AnimatePresence> </LazyMotion> </NerioMotionConfig> );}The adapter entrypoint is client-only. Keep the use client boundary at the smallest interactive composition. NerioMotionConfig always sets reducedMotion="user". Use initial={false} when server-rendered state is already visible so hydration does not replay an entrance. useNerioReducedMotion provides a server-stable snapshot and updates mounted compositions when the operating-system preference changes.
domAnimation covers variants, presence, and tap, hover, and focus gestures. Load domMax only for layout, pan, or drag capabilities. Strict mode catches an accidental full motion component inside a LazyMotion boundary; use m from motion/react-m there.
Focused examples
These examples keep animation restrained and expose stable end states for browser verification. They do not replace existing Core component motion.
Presence
A coordinated enter and exit that preserves a deterministic end state.
Interruption
Rapid reversals continue from the current value instead of restarting.
Layout
The larger feature bundle is loaded only for layout continuity.
Reduced motion
User preference removes transform travel while preserving opacity and state.
Choose the smallest motion layer
| Layer | Use for | Avoid when |
|---|---|---|
| CSS and Tailwind | Hover, press, focus, color, opacity, small transforms, and reliable Base UI states. | The change requires coordinated presence, interruption, or shared layout. |
| Optional Motion adapter | Presence, interruption, layout continuity, gestures, and product-level sequences. | A shared CSS recipe already expresses the complete interaction. |
| View Transitions API | Browser-owned page or route transitions with stable old and new document states. | The interaction is gesture-driven or needs element-level interruption. |
Reduced motion
| Preference | Behavior | Examples |
|---|---|---|
| Default | Core components use short durations and avoid layout-heavy motion. | Hover, focus, overlays, and skeleton loading resolve through CSS variables. |
| Reduced motion | Large movement collapses to opacity or immediate state changes. | Press scale, overlay translation, and repeated skeleton movement are removed or minimized. |
Usage
import { cn, motionClasses } from "@nerio-ui/ui";<button type="button" className={cn( "rounded-n-control border border-n-border bg-n-surface px-n-4 py-n-2 text-n-text focus-visible:outline-0 focus-visible:shadow-(--n-focus-ring)", motionClasses.interactive, )}> Save changes</button>- Use shared motion recipes before adding component-specific Tailwind transitions.
- Keep Core motion subtle; expressive motion belongs in product compositions and Pro examples.
- Do not add animation libraries for basic hover, focus, overlay, or loading behavior.
Source installation and removal
pnpm add motionpnpm nerio add motion-adapterimport { NerioMotionConfig, motionTransitions, motionVariants, useNerioReducedMotion,} from "@/components/nerio/lib/motion-adapter";Removing the adapter does not change Core components: replace Motion compositions with CSS recipes or static end states, remove the copied adapter file, then uninstall motion. No hidden provider or Core migration is required.