Help us improve
Share bugs, ideas, or general feedback.
Provides pure CSS animation expertise using keyframes, transitions, timing functions, and Tailwind utilities for lightweight, performant effects without JavaScript.
npx claudepluginhub markus41/claude --plugin react-animation-studioHow this skill is triggered — by the user, by Claude, or both
Slash command
/react-animation-studio:css-animationsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Expert knowledge for pure CSS animations - lightweight, performant animations using CSS keyframes, transitions, and modern CSS features without JavaScript dependencies.
Creates performant CSS animations using Tailwind transitions, keyframe utilities, and motion-safe patterns for accessible, GPU-accelerated motion.
Provides Tailwind CSS built-in animations (spin, ping, pulse, bounce) and transition utilities (properties, durations, easings, delays) with HTML examples. Use for loading indicators, notifications, hovers, and UI motion.
Adds animation polish to web apps: button press feedback, hover states, modal transitions, loading skeletons, toasts. Prioritizes CSS/Tailwind, respects reduced motion.
Share bugs, ideas, or general feedback.
Expert knowledge for pure CSS animations - lightweight, performant animations using CSS keyframes, transitions, and modern CSS features without JavaScript dependencies.
Activate this skill when:
**/*.css with @keyframes definitions**/*.scss or **/*.sass with animations**/*.tsx with Tailwind animation classes**/styles/*.css with animation utilitiestailwind.config.js with custom animations.element {
transition: transform 0.3s ease-out, opacity 0.3s ease-out;
}
.element:hover {
transform: translateY(-4px);
opacity: 0.9;
}
/* Shorthand */
.element {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.element {
animation: fadeIn 0.5s ease-out forwards;
}
.element {
animation-name: slideIn;
animation-duration: 0.5s;
animation-timing-function: ease-out;
animation-delay: 0.2s;
animation-iteration-count: 1; /* or infinite */
animation-direction: normal; /* or reverse, alternate, alternate-reverse */
animation-fill-mode: forwards; /* none, forwards, backwards, both */
animation-play-state: running; /* or paused */
/* Shorthand */
animation: slideIn 0.5s ease-out 0.2s 1 normal forwards;
}
/* Built-in */
transition-timing-function: linear;
transition-timing-function: ease;
transition-timing-function: ease-in;
transition-timing-function: ease-out;
transition-timing-function: ease-in-out;
/* Cubic bezier */
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); /* Material ease */
transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1); /* Default ease */
transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55); /* Back ease */
/* Steps (for sprite animations) */
transition-timing-function: steps(4, end);
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translate3d(0, 20px, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translate3d(0, -20px, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
}
@keyframes bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
@keyframes dotPulse {
0%, 80%, 100% {
transform: scale(0);
opacity: 0.5;
}
40% {
transform: scale(1);
opacity: 1;
}
}
<div class="animate-spin">Loading...</div>
<div class="animate-ping">Notification</div>
<div class="animate-pulse">Skeleton</div>
<div class="animate-bounce">Scroll down</div>
// tailwind.config.js
module.exports = {
theme: {
extend: {
animation: {
'fade-in': 'fadeIn 0.5s ease-out forwards',
'fade-in-up': 'fadeInUp 0.5s ease-out forwards',
'slide-in-right': 'slideInRight 0.3s ease-out forwards',
'scale-in': 'scaleIn 0.2s ease-out forwards',
'shimmer': 'shimmer 2s infinite',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
fadeInUp: {
'0%': { opacity: '0', transform: 'translateY(20px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
slideInRight: {
'0%': { opacity: '0', transform: 'translateX(20px)' },
'100%': { opacity: '1', transform: 'translateX(0)' },
},
scaleIn: {
'0%': { opacity: '0', transform: 'scale(0.95)' },
'100%': { opacity: '1', transform: 'scale(1)' },
},
shimmer: {
'0%': { backgroundPosition: '-200% 0' },
'100%': { backgroundPosition: '200% 0' },
},
},
},
},
};
.stagger-item {
animation: fadeInUp 0.5s ease-out forwards;
animation-delay: calc(var(--index) * 0.1s);
opacity: 0;
}
{items.map((item, index) => (
<div
key={item.id}
className="stagger-item"
style={{ '--index': index } as React.CSSProperties}
>
{item.content}
</div>
))}
// components/Spinner.tsx
export function Spinner({ size = 'md' }: { size?: 'sm' | 'md' | 'lg' }) {
const sizes = {
sm: 'w-4 h-4 border-2',
md: 'w-8 h-8 border-3',
lg: 'w-12 h-12 border-4',
};
return (
<div
className={`${sizes[size]} rounded-full border-blue-500 border-t-transparent animate-spin`}
/>
);
}
// components/Skeleton.tsx
export function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={`bg-gray-200 rounded animate-pulse ${className}`}
{...props}
/>
);
}
// Usage
<div className="space-y-2">
<Skeleton className="h-4 w-3/4" />
<Skeleton className="h-4 w-1/2" />
<Skeleton className="h-4 w-5/6" />
</div>
.shimmer {
background: linear-gradient(
90deg,
#f0f0f0 0%,
#e0e0e0 50%,
#f0f0f0 100%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
/* Good - GPU accelerated */
transform: translateX(100px);
transform: scale(1.1);
transform: rotate(45deg);
opacity: 0.5;
/* Avoid - trigger layout/paint */
width: 100px;
height: 100px;
top: 10px;
left: 10px;
margin: 10px;
padding: 10px;
.animated-element {
will-change: transform, opacity;
}
/* Remove after animation */
.animated-element.done {
will-change: auto;
}
/* Bad - triggers layout */
.slide {
left: 0;
transition: left 0.3s;
}
.slide.active {
left: 100px;
}
/* Good - GPU accelerated */
.slide {
transform: translateX(0);
transition: transform 0.3s;
}
.slide.active {
transform: translateX(100px);
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Or provide alternatives */
@media (prefers-reduced-motion: reduce) {
.animated-element {
animation: none;
opacity: 1;
}
}
.card:hover { transform: translateY(-4px); box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15); }
Or with Tailwind:
```tsx
<div className="transition-all duration-200 ease-out hover:-translate-y-1 hover:shadow-xl">
Card content
</div>
Context: User needs a loading skeleton
user: "Create a skeleton loading state for this content"
assistant: Using CSS shimmer animation:
```tsx
function ContentSkeleton() {
return (
Created by Brookside BI as part of React Animation Studio