Animations Getting Started

This section introduces the TaylorUI animation approach:

  • Build with Tailwind-native @utility animation 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>
UtilityDescription
animate-in-fadeFades in
animate-in-fade-upFades in from below
animate-in-fade-downFades in from above
animate-in-fade-leftFades in from the right
animate-in-fade-rightFades in from the left
animate-in-zoomScales up from 85%
animate-in-spinFades + scales + rotates in
animate-out-fadeFades out
animate-out-fade-upFades out moving up
animate-out-fade-downFades out moving down
animate-out-fade-leftFades out moving left
animate-out-fade-rightFades out moving right
animate-out-zoomScales down and fades out
animate-out-spinFades + scales + rotates out
animate-spinInfinite rotation (Tailwind native)
animate-pingPing effect (Tailwind native)
animate-pulsePulse effect (Tailwind native)
animate-bounceBounce 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>
ModifierDescription
fade-in / fade-outOpacity from/to 0
spin-in / spin-outRotate 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>;
PropTypeDefaultDescription
animationstring'fade-up'fade-up, fade-in, fade-down, …
delaynumberStagger 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>;
PropTypeDefaultDescription
children[node, node]Two children: default then swapped
durationnumber1500ms before reverting
classNamestringStyles passed to the wrapper
statusLabelstringScreen-reader text on swap
onClick() => voidAdditional 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 */