Claude-skill-registry animation-interaction-validator
Ensures engaging user experience through validation of animations, transitions, micro-interactions, and feedback states, preventing flat/static interfaces that lack polish and engagement. Works with Tanstack Start (React) + shadcn/ui components.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/animation-interaction-validator" ~/.claude/skills/majiayu000-claude-skill-registry-animation-interaction-validator && rm -rf "$T"
manifest:
skills/data/animation-interaction-validator/SKILL.mdsource content
Animation Interaction Validator SKILL
Activation Patterns
This SKILL automatically activates when:
- Interactive elements are created (buttons, links, forms, inputs)
- Click, hover, or focus event handlers are added
- Component state changes (loading, success, error)
- Async operations are initiated (API calls, form submissions)
- Navigation or routing transitions occur
- Modal/dialog components are opened/closed
- Lists or data are updated dynamically
Expertise Provided
Animation & Interaction Validation
- Transition Detection: Ensures smooth state changes with CSS transitions
- Hover State Validation: Checks for hover feedback on interactive elements
- Loading State Validation: Ensures async actions have visual feedback
- Micro-interaction Analysis: Validates small, delightful animations
- Focus State Validation: Ensures keyboard navigation has visual feedback
- Animation Performance: Checks for performant animation patterns
Specific Checks Performed
❌ Critical Issues (Missing Feedback)
// These patterns trigger alerts: // No hover state <Button onClick={submit}>Submit</Button> // No loading state during async action <Button onClick={async () => await submitForm()}>Save</Button> // Jarring state change (no transition) {showContent && <div>Content</div>} // No focus state <a href="/page" className="text-blue-500">Link</a> // Form without feedback <form onSubmit={handleSubmit}> <Input value={value} onChange={setValue} /> <button type="submit">Submit</button> </form>
✅ Correct Interactive Patterns
import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Send } from "lucide-react" import { cn } from "@/lib/utils" // These patterns are validated as correct: // Hover state with smooth transition <Button className="transition-all duration-300 hover:scale-105 hover:shadow-xl active:scale-95" onClick={submit} > Submit </Button> // Loading state with visual feedback <Button disabled={isSubmitting} className="transition-all duration-200 group" onClick={handleSubmit} > <span className="flex items-center gap-2"> {!isSubmitting && ( <Send className="h-4 w-4 transition-transform duration-300 group-hover:translate-x-1" /> )} {isSubmitting ? 'Submitting...' : 'Submit'} </span> </Button> // Smooth state transition (using framer-motion or CSS) <div className={cn( "transition-all duration-300 ease-out", showContent ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4" )} > {showContent && <div>Content</div>} </div> // Focus state with ring <a href="/page" className="text-blue-500 transition-colors duration-200 hover:text-blue-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2" > Link </a> <!-- Form with success/error feedback --> <form onSubmit={(e) => { e.preventDefault(); handleSubmit" className="space-y-4"> <Input value="value" error={errors.value" className="transition-all duration-200" /> <Button type="submit" loading={isSubmitting" disabled={isSubmitting" className="transition-all duration-300 hover:scale-105" > Submit </Button> <!-- Success message with animation --> <Transition name="fade"> <Alert if="showSuccess" color="green" icon="i-heroicons-check-circle" title="Success!" className="animate-in slide-in-from-top" /> </Transition> </form>
Integration Points
Complementary to Existing Components
- frontend-design-specialist agent: Provides design direction, SKILL validates implementation
- component-aesthetic-checker: Validates component customization, SKILL validates interactions
- shadcn-ui-design-validator: Catches generic patterns, SKILL ensures engagement
- accessibility-guardian agent: Validates a11y, SKILL validates visual feedback
Escalation Triggers
- Complex animation sequences →
agentfrontend-design-specialist - Component interaction patterns →
agenttanstack-ui-architect - Performance concerns →
agentedge-performance-oracle - Accessibility issues →
agentaccessibility-guardian
Validation Rules
P1 - Critical (Missing User Feedback)
- No Hover States: Buttons/links without hover effects
- No Loading States: Async actions without loading indicators
- Jarring State Changes: Content appearing/disappearing without transitions
- No Focus States: Interactive elements without keyboard focus indicators
- Silent Errors: Form errors without visual feedback
P2 - Important (Enhanced Engagement)
- No Micro-interactions: Icons/elements without subtle animations
- Static Navigation: Page transitions without animations
- Abrupt Modals: Dialogs opening without enter/exit transitions
- Instant Updates: List changes without transition animations
- No Disabled States: Buttons during processing without visual change
P3 - Polish (Delightful UX)
- Limited Animation Variety: Using only scale/opacity (no rotate, translate)
- Generic Durations: Not tuning animation speed for context
- No Stagger: List items appearing simultaneously (no stagger effect)
- Missing Success States: Completed actions without celebration animation
- No Hover Anticipation: No visual hint before interaction is possible
Remediation Examples
Fixing Missing Hover States
<!-- ❌ Critical: No hover feedback --> <Button onClick="handleClick"> Click me </Button> <!-- ✅ Correct: Multi-dimensional hover effects --> <Button className=" transition-all duration-300 ease-out hover:scale-105 hover:shadow-xl hover:-rotate-1 active:scale-95 active:rotate-0 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary-500 " onClick="handleClick" > <span className="inline-flex items-center gap-2"> Click me <Icon name="i-heroicons-arrow-right" className="transition-transform duration-300 group-hover:translate-x-1" /> </span> </Button>
Fixing Missing Loading States
<!-- ❌ Critical: No loading feedback during async action --> const submitForm = async () => { await api.submit(formData); }; <Button onClick="submitForm"> Submit </Button> <!-- ✅ Correct: Complete loading state with animations --> const isSubmitting = ref(false); const showSuccess = ref(false); const submitForm = async () => { isSubmitting.value = true; try { await api.submit(formData); showSuccess.value = true; setTimeout(() => showSuccess.value = false, 3000); } catch (error) { // Error handling } finally { isSubmitting.value = false; } }; <div className="space-y-4"> <Button loading={isSubmitting" disabled={isSubmitting" className=" transition-all duration-300 hover:scale-105 hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed " onClick="submitForm" > <span className="flex items-center gap-2"> <Icon if="!isSubmitting" name="i-heroicons-paper-airplane" className="transition-all duration-300 group-hover:translate-x-1 group-hover:-translate-y-1" /> { isSubmitting ? 'Submitting...' : 'Submit'} </span> </Button> <!-- Success feedback with animation --> <Transition enter-active-className="transition-all duration-500 ease-out" enter-from-className="opacity-0 scale-50" enter-to-className="opacity-100 scale-100" leave-active-className="transition-all duration-300 ease-in" leave-from-className="opacity-100 scale-100" leave-to-className="opacity-0 scale-50" > <Alert if="showSuccess" color="green" icon="i-heroicons-check-circle" title="Success!" description="Your form has been submitted." /> </Transition> </div>
Fixing Jarring State Changes
<!-- ❌ Critical: Content appears/disappears abruptly --> <div> <Button onClick="showContent = !showContent"> Toggle </Button> <div if="showContent"> <p>This content appears instantly (jarring)</p> </div> </div> <!-- ✅ Correct: Smooth transitions --> <div className="space-y-4"> <Button className="transition-all duration-300 hover:scale-105" onClick="showContent = !showContent" > { showContent ? 'Hide' : 'Show'} Content </Button> <Transition enter-active-className="transition-all duration-300 ease-out" enter-from-className="opacity-0 translate-y-4 scale-95" enter-to-className="opacity-100 translate-y-0 scale-100" leave-active-className="transition-all duration-200 ease-in" leave-from-className="opacity-100 translate-y-0 scale-100" leave-to-className="opacity-0 translate-y-4 scale-95" > <div if="showContent" className="p-6 bg-gray-50 dark:bg-gray-800 rounded-lg"> <p>This content transitions smoothly</p> </div> </Transition> </div>
Fixing Missing Focus States
<!-- ❌ Critical: No visible focus state --> <nav> <a href="/" className="text-gray-700">Home</a> <a href="/about" className="text-gray-700">About</a> <a href="/contact" className="text-gray-700">Contact</a> </nav> <!-- ✅ Correct: Clear focus states for keyboard navigation --> <nav className="flex gap-4"> <a href="/" className=" text-gray-700 dark:text-gray-300 transition-all duration-200 hover:text-primary-600 hover:translate-y-[-2px] focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 rounded px-3 py-2 " > Home </a> <a href="/about" className=" text-gray-700 dark:text-gray-300 transition-all duration-200 hover:text-primary-600 hover:translate-y-[-2px] focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 rounded px-3 py-2 " > About </a> <a href="/contact" className=" text-gray-700 dark:text-gray-300 transition-all duration-200 hover:text-primary-600 hover:translate-y-[-2px] focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 rounded px-3 py-2 " > Contact </a> </nav>
Adding Micro-interactions
<!-- ❌ P2: Static icons without micro-interactions --> <Button icon="i-heroicons-heart"> Like </Button> <!-- ✅ Correct: Animated icon micro-interaction --> const isLiked = ref(false); const heartScale = ref(1); const toggleLike = () => { isLiked.value = !isLiked.value; // Bounce animation heartScale.value = 1.3; setTimeout(() => heartScale.value = 1, 200); }; <Button :color="isLiked ? 'red' : 'gray'" className="transition-all duration-300 hover:scale-105" onClick="toggleLike" > <span className="inline-flex items-center gap-2"> <Icon :name="isLiked ? 'i-heroicons-heart-solid' : 'i-heroicons-heart'" :style="{ transform: `scale(${heartScale})` }" :className="[ 'transition-all duration-200', isLiked ? 'text-red-500 animate-pulse' : 'text-gray-500' ]" /> { isLiked ? 'Liked' : 'Like'} </span> </Button>
Animation Best Practices
Performance-First Animations
✅ Performant Properties (GPU-accelerated):
(translate, scale, rotate)transformopacity
(backdrop-blur, etc.)filter
❌ Avoid Animating (causes reflow/repaint):
,widthheight
,top
,left
,rightbottom
,marginpaddingborder-width
<!-- ❌ P2: Animating width (causes reflow) --> <div className="transition-all hover:w-64">Content</div> <!-- ✅ Correct: Using transform (GPU-accelerated) --> <div className="transition-transform hover:scale-110">Content</div>
Animation Duration Guidelines
- Fast (100-200ms): Hover states, small movements
- Medium (300-400ms): State changes, content transitions
- Slow (500-800ms): Page transitions, major UI changes
- Very Slow (1000ms+): Celebration animations, complex sequences
<!-- Context-appropriate durations --> <Button className="transition-all duration-200 hover:scale-105"> <!-- Fast hover: 200ms --> </Button> <Transition enter-active-className="transition-all duration-300" leave-active-className="transition-all duration-300" > <!-- Content change: 300ms --> <div if="show">Content</div> </Transition> <div className="animate-in slide-in-from-bottom duration-500"> <!-- Page load: 500ms --> Main content </div>
Easing Functions
: Starting animations (entering content)ease-out
: Ending animations (exiting content)ease-in
: Bidirectional animationsease-in-out
: Loading spinners, continuous animationslinear
<!-- Appropriate easing --> <Transition enter-active-className="transition-all duration-300 ease-out" leave-active-className="transition-all duration-200 ease-in" > <div if="show">Content</div> </Transition>
Advanced Interaction Patterns
Staggered List Animations
const items = ref([1, 2, 3, 4, 5]); <TransitionGroup name="list" tag="div" className="space-y-2" > <div map((item, index) in items" :key="item" :style="{ transitionDelay: `${index * 50}ms` }" className=" transition-all duration-300 ease-out hover:scale-105 hover:shadow-lg " > Item { item} </div> </TransitionGroup> <style scoped> .list-enter-active, .list-leave-active { transition: all 0.3s ease; } .list-enter-from { opacity: 0; transform: translateX(-20px); } .list-leave-to { opacity: 0; transform: translateX(20px); } .list-move { transition: transform 0.3s ease; } </style>
Success Celebration Animation
const showSuccess = ref(false); const celebrate = () => { showSuccess.value = true; // Confetti or celebration animation here setTimeout(() => showSuccess.value = false, 3000); }; <div> <Button onClick="celebrate" className="transition-all duration-300 hover:scale-110 hover:rotate-3" > Complete Task </Button> <Transition enter-active-className="transition-all duration-500 ease-out" enter-from-className="opacity-0 scale-0 rotate-180" enter-to-className="opacity-100 scale-100 rotate-0" > <div if="showSuccess" className="fixed inset-0 flex items-center justify-center bg-black/20 backdrop-blur-sm" > <div className="bg-white dark:bg-gray-800 p-8 rounded-2xl shadow-2xl"> <Icon name="i-heroicons-check-circle" className="w-16 h-16 text-green-500 animate-bounce" /> <p className="mt-4 text-xl font-heading">Success!</p> </div> </div> </Transition> </div>
Loading Skeleton with Pulse
<div if="loading" className="space-y-4"> <div className="animate-pulse"> <div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-3/4"></div> <div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-1/2 mt-2"></div> <div className="h-32 bg-gray-200 dark:bg-gray-700 rounded mt-4"></div> </div> </div> <Transition enter-active-className="transition-all duration-500 ease-out" enter-from-className="opacity-0 translate-y-4" enter-to-className="opacity-100 translate-y-0" > <div if="!loading"> <!-- Actual content --> </div> </Transition>
MCP Server Integration
While this SKILL doesn't directly use MCP servers, it complements MCP-enhanced agents:
- shadcn/ui MCP: Validates that suggested animations work with shadcn/ui components
- Cloudflare MCP: Ensures animations don't bloat bundle size (performance check)
Benefits
Immediate Impact
- Prevents Flat UI: Ensures engaging, polished interactions
- Improves Perceived Performance: Loading states make waits feel shorter
- Better Accessibility: Focus states improve keyboard navigation
- Professional Polish: Micro-interactions signal quality
Long-term Value
- Higher User Engagement: Delightful animations encourage interaction
- Reduced Bounce Rate: Polished UI keeps users engaged
- Better Brand Perception: Professional animations signal quality
- Consistent UX: All interactions follow same animation patterns
Usage Examples
During Button Creation
// Developer adds: <Button onClick="submit">Submit</Button> // SKILL immediately activates: "⚠️ P1: Button lacks hover state. Add transition utilities: class='transition-all duration-300 hover:scale-105'"
During Async Action
// Developer creates: const submitForm = async () => { await api.call(); } // SKILL immediately activates: "⚠️ P1: Async action without loading state. Add :loading and :disabled props to button."
During State Toggle
// Developer adds: <div if="show">Content</div> // SKILL immediately activates: "⚠️ P1: Content appears abruptly. Wrap with <Transition> for smooth state changes."
Before Deployment
// SKILL runs comprehensive check: "✅ Animation validation passed. 45 interactive elements with hover states, 12 async actions with loading feedback, 8 smooth transitions detected."
This SKILL ensures every interactive element provides engaging visual feedback, preventing the flat, static appearance that makes interfaces feel unpolished and reduces user engagement.