# tw-animate-css for Micro Animations

**SHOULD** · **ID:** `animations-ibelick-tw-animate` · **Category:** animations
**Source:** [@Ibelick](https://www.ui-skills.com/)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/animations-ibelick-tw-animate

> SHOULD: Use tw-animate-css for entrance and micro-animations in Tailwind CSS. Provides consistent, performant CSS animations without JavaScript overhead.

Use tw-animate-css plugin for entrance and micro-interaction animations

This is a SHOULD, not a MUST, and it is scoped to Tailwind projects. The point of it is that entrance and micro-interaction animations are the ones most often hand-rolled per component — a bespoke @keyframes here, an inline style there — which is how a product ends up with six slightly different fade-ins. tw-animate-css supplies them as utilities (fade-in, slide-in-from-bottom-4, zoom-in-95) so the whole surface shares one vocabulary. It is CSS-only, which also keeps it on the right side of animations-implementation-preference.

## Bad — do not do this

`animations-ibelick-tw-animate-bad`

```tsx
import { useState } from 'react';

export function IbelickTwAnimateBad() {
  const [replay, setReplay] = useState(0);

  return (
    <div className="space-y-4">
      <button
        onClick={() => setReplay((v) => v + 1)}
        className="px-4 py-2 rounded-lg bg-primary text-primary-foreground transition-colors duration-150 ease-out hover:bg-primary/90"
      >
        Replay
      </button>
      <div
        key={replay}
        className="p-4 bg-muted rounded-lg text-sm"
        // No animation-fill-mode + a delay: the item shows at its final state,
        // then snaps to opacity 0 when the animation starts — a visible flash.
        style={{ animation: 'twBadFade 400ms ease-out 250ms' }}
      >
        Hand-rolled keyframes, no fill-mode
      </div>
      <style>{`
        @keyframes twBadFade {
          from { opacity: 0; transform: translateY(8px); }
          to { opacity: 1; transform: translateY(0); }
        }
      `}</style>
      <p className="text-xs text-destructive">
        Custom keyframes with no <code>animation-fill-mode</code> — the item flashes at its final state before it animates in
      </p>
    </div>
  );
}
```

## Good — do this

`animations-ibelick-tw-animate-good`

```tsx
import { useState } from 'react';

export function IbelickTwAnimateGood() {
  const [replay, setReplay] = useState(0);

  return (
    <div className="space-y-4">
      <button
        onClick={() => setReplay((v) => v + 1)}
        className="px-4 py-2 rounded-lg bg-primary text-primary-foreground transition-colors duration-150 ease-out hover:bg-primary/90"
      >
        Replay
      </button>
      <div
        key={replay}
        className="p-4 bg-muted rounded-lg text-sm animate-in fade-in slide-in-from-bottom-2 duration-300"
      >
        tw-animate-css utility (fill-mode handled)
      </div>
      <p className="text-xs text-success">
        A ready-made utility handles <code>fill-mode</code> and easing — no flash, no custom keyframes to maintain
      </p>
    </div>
  );
}
```

## References

- [ibelick — baseline-ui SKILL.md](https://github.com/ibelick/ui-skills/blob/main/skills/baseline-ui/SKILL.md)
- [tw-animate-css](https://github.com/Wombosvideo/tw-animate-css)
