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)

Use CSS first for most UI states. Each animation is a Tailwind @utility class that works like any other utility — combine with variants, compose with other classes, and get autocomplete in your editor.

{
  /* Trigger on data-state="open" (hover, click, accordion, etc.) */
}
<div className="data-[state=open]:animate-fade-up" data-state="closed">
  Animated element
</div>;

Available animation utilities:

UtilityDescription
animate-fade-upFades in from below
animate-fade-downFades in from above
animate-fade-inSimple fade in
animate-fade-leftFades in from the right
animate-fade-rightFades in from the left
animate-zoom-inScales up from 95%
animate-spinInfinite rotation (tailwind native)
animate-pingPing effect (tailwind native)
animate-pulsePulse effect (tailwind native)
animate-bounceBounce effect (tailwind native)

Toggle data-state yourself to trigger CSS utilities on hover, click, or other interactions:

<div
  className="data-[state=open]:animate-fade-up"
  data-state={isVisible ? 'open' : 'closed'}
>
  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';