Animations Getting Started
This section introduces the TaylorUI animation approach:
- Build with Tailwind-native
@utilityanimation classes. - Use Framer Motion when interaction or orchestration needs JS.
- Reuse shared variants and presets from
@/animations.
1. CSS animation library (default)
The CSS animation system has two layers:
Convenience presets — single-class animations with duration and easing
defaulting to the design tokens. Reach for these by default. Override timing
with duration-* and ease-* when the defaults aren't right.
animate-in-* presets default to slow (500ms) + ease-out.
animate-out-* presets default to fast (150ms) + ease-in.
{/* default timing from tokens */}
<div className="data-[state=visible]:animate-in-fade-up" data-state="hidden">
Animated element
</div>
{/* tuned timing */}
<div className="data-[state=visible]:animate-in-fade-up duration-200" data-state="hidden">
Animated element
</div>| Utility | Description |
|---|---|
animate-in-fade | Fades in |
animate-in-fade-up | Fades in from below |
animate-in-fade-down | Fades in from above |
animate-in-fade-left | Fades in from the right |
animate-in-fade-right | Fades in from the left |
animate-in-zoom | Scales up from 85% |
animate-in-spin | Fades + scales + rotates in |
animate-out-fade | Fades out |
animate-out-fade-up | Fades out moving up |
animate-out-fade-down | Fades out moving down |
animate-out-fade-left | Fades out moving left |
animate-out-fade-right | Fades out moving right |
animate-out-zoom | Scales down and fades out |
animate-out-spin | Fades + scales + rotates out |
animate-spin | Infinite rotation (Tailwind native) |
animate-ping | Ping effect (Tailwind native) |
animate-pulse | Pulse effect (Tailwind native) |
animate-bounce | Bounce effect (Tailwind native) |
Base layer — composable primitives for full control. Use when no preset covers your combination — e.g. a directional slide with custom timing, or mixing enter properties the presets don't expose.
<div className="animate-in fade-in slide-in-from-top-1 duration-200 ease-out">
Entering element
</div>
<div className="animate-out fade-out slide-out-to-top-1 duration-150 ease-in">
Exiting element
</div>| Modifier | Description |
|---|---|
fade-in / fade-out | Opacity from/to 0 |
spin-in / spin-out | Rotate from −30° / to +30° |
slide-in-from-top-{n} | Slide in from above (spacing scale) |
slide-in-from-bottom-{n} | Slide in from below |
slide-in-from-left-{n} | Slide in from the left |
slide-in-from-right-{n} | Slide in from the right |
slide-out-to-* | Mirror of slide-in for exit |
Toggle data-state yourself to trigger presets on hover, click, or other
interactions:
<div
className="data-[state=hidden]:opacity-0 data-[state=visible]:animate-in-fade-up"
data-state={isVisible ? 'visible' : 'hidden'}
>
Controlled element
</div>For scroll-into-view reveals, use MotionReveal instead.
2. Framer Motion (when CSS is not enough)
Use Framer for gesture-driven interactions, coordinated stagger timelines, or route-level transitions that are easier to express in JS.
import { motion } from 'framer-motion';
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />;3. Reusing our variants and presets
Import directly from @/animations to keep motion behavior consistent across
projects.
import { motion } from 'framer-motion';
import { fadeUp, pageTransition, staggerContainer } from '@/animations';
<motion.ul variants={staggerContainer} initial="hidden" animate="visible">
<motion.li variants={fadeUp}>Item one</motion.li>
<motion.li variants={fadeUp}>Item two</motion.li>
</motion.ul>;
<motion.div {...pageTransition}>Animated page content</motion.div>;4. MotionReveal
Universal motion-reveal wrapper for any child — grids, sections, cards. Wraps
content in motion.div, animates on viewport enter using shared Framer variants
(hidden / visible) from @/animations. Stagger via the delay prop (ms).
import { MotionReveal } from '@/animations';
<div className="grid gap-6 sm:grid-cols-2">
{articles.map((article, index) => (
<MotionReveal key={article.id} animation="fade-up" delay={index * 100}>
<Card {...article} />
</MotionReveal>
))}
</div>;| Prop | Type | Default | Description |
|---|---|---|---|
animation | string | 'fade-up' | fade-up, fade-in, fade-down, … |
delay | number | — | Stagger delay in milliseconds |
CardSkeleton mirrors the demo Card layout for loading states — same grid on
the Motion reveal page, with an On / Off toggle.
See that page for a full grid example with animation picker and skeleton preview.
5. CSS hover underline utilities
A single @utility class for animated underlines built with Tailwind's
@apply. The underline slides in from the left on hover and retreats
to the right on exit.
<a href="#" class="hover-underline">Hover me</a>Works with group hover too — just add group to the parent:
<div class="group">
<RiHeartLine />
<span class="hover-underline">Group hover underline</span>
</div>6. SlideUpOnGroupHover
A wrapper component that slides content up on parent hover, revealing a
duplicate underneath. Works inside any existing Button or ButtonLink.
import {
SlideUpOnGroupHover,
slideUpOnHoverGroupClassName,
} from '@/animations';
<Button className={slideUpOnHoverGroupClassName}>
<SlideUpOnGroupHover>Click me</SlideUpOnGroupHover>
</Button>;With an icon:
<Button
className={slideUpOnHoverGroupClassName}
variant="primary"
icon={<RiArrowRightCircleLine />}
>
<SlideUpOnGroupHover>With icon</SlideUpOnGroupHover>
</Button>7. ConfettiButton
A Framer Motion wrapper that spawns colored confetti particles on click.
Wrap it around a <Button /> component to add the confetti effect.
import { ConfettiButton } from '@/animations';
import { Button } from '@/ui/components/button/button';
<ConfettiButton>
<Button variant="primary">Click for confetti!</Button>
</ConfettiButton>;8. ClickContentSwap
Swaps content with a smooth enter/exit animation on click, then reverts after a configurable delay. Pass two children: the first is the default content, the second is the content shown after clicking.
Great for "Copy to clipboard" or "Saved!" feedback.
import { RiCheckLine } from '@remixicon/react';
import { ClickContentSwap } from '@/animations';
import { Button } from '@/ui/components/button/button';
<ClickContentSwap>
<Button variant="primary">Copy to clipboard</Button>
<span className="flex items-center gap-2">
<RiCheckLine />
Copied!
</span>
</ClickContentSwap>;| Prop | Type | Default | Description |
|---|---|---|---|
children | [node, node] | — | Two children: default then swapped |
duration | number | 1500 | ms before reverting |
className | string | — | Styles passed to the wrapper |
statusLabel | string | — | Screen-reader text on swap |
onClick | () => void | — | Additional click handler |
9. Page Transitions:
TODO: add more information
Try out a page transition (swipe-right)
What to copy into another project
src/animations/css/src/animations/framer/src/animations/components/src/animations/index.ts
In your global stylesheet, include:
@import '../animations/css/animations.css'; /* includes tokens.css */