Marketplace

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.

$ Installer

git clone https://github.com/hirefrank/hirefrank-marketplace /tmp/hirefrank-marketplace && cp -r /tmp/hirefrank-marketplace/plugins/edge-stack/skills/animation-interaction-validator ~/.claude/skills/hirefrank-marketplace

// tip: Run this command in your terminal to install the skill


name: animation-interaction-validator description: 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. triggers: ["interactive element creation", "event handler addition", "state changes", "async actions", "form submissions"] note: "Code examples use React/TSX with shadcn/ui components (Button, Card, Input). Adapt patterns to your component library."

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 → frontend-design-specialist agent
  • Component interaction patterns → tanstack-ui-architect agent
  • Performance concerns → edge-performance-oracle agent
  • Accessibility issues → accessibility-guardian agent

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):

  • transform (translate, scale, rotate)
  • opacity
  • filter (backdrop-blur, etc.)

Avoid Animating (causes reflow/repaint):

  • width, height
  • top, left, right, bottom
  • margin, padding
  • border-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

  • ease-out: Starting animations (entering content)
  • ease-in: Ending animations (exiting content)
  • ease-in-out: Bidirectional animations
  • linear: Loading spinners, continuous animations
<!-- 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.

Repository

hirefrank
hirefrank
Author
hirefrank/hirefrank-marketplace/plugins/edge-stack/skills/animation-interaction-validator
2
Stars
0
Forks
Updated6d ago
Added1w ago