design/motion-design/skills/microinteraction-library/SKILL.md
This skill should be used when the user needs production-ready animation patterns for specific UI states or feedback moments. Trigger when the user mentions "loading state", "skeleton screen", "loading spinner", "error animation", "error shake", "success animation", "success checkmark", "form feedback", "button loading state", "checkbox animation", "toggle animation", "notification badge", "like animation", "copy to clipboard feedback", "progress indicator", "toast notification animation", or wants to add "polish" or "juice" to individual UI components. Also trigger when the user asks how to animate specific component states (empty state, error state, success state, pending state).
npx skillsauth add harsh040506/claude-code-unified-skill-plugin-library microinteraction-libraryInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Microinteractions define product feel and polish. Each one is a small, specific, meaningful moment of feedback — a confirmation that the product is alive and responding. The goal is not novelty but clarity and delight in equal measure.
Design principle: Every microinteraction must earn cognitive attention. Animations that are too slow, too large, or too frequent become visual noise. The best microinteractions are noticed when absent, not when present.
Skeleton Screen (preferred over spinners)
Use when: content shape is known ahead of load. Reduces disorientation by showing the incoming structure.
function SkeletonCard() {
return (
<div className="skeleton-card">
<div className="skeleton-avatar" />
<div className="skeleton-lines">
<div className="skeleton-line skeleton-line--wide" />
<div className="skeleton-line skeleton-line--medium" />
<div className="skeleton-line skeleton-line--narrow" />
</div>
</div>
);
}
.skeleton-line,
.skeleton-avatar {
background: #e0e0e0;
border-radius: 4px;
animation: skeletonPulse 1.5s ease-in-out infinite;
}
/* Stagger the wave across elements */
.skeleton-line:nth-child(2) { animation-delay: 150ms; }
.skeleton-line:nth-child(3) { animation-delay: 300ms; }
@keyframes skeletonPulse {
0%, 100% { opacity: 0.4; }
50% { opacity: 1; }
}
/* Shimmer variant — more polished */
.skeleton-shimmer {
background: linear-gradient(90deg, #e0e0e0 25%, #f0f0f0 50%, #e0e0e0 75%);
background-size: 200% 100%;
animation: shimmerSlide 1.5s infinite;
}
@keyframes shimmerSlide {
from { background-position: 200% 0; }
to { background-position: -200% 0; }
}
Button Loading State
Transition label → spinner on async action; return to label on completion:
function AsyncButton({ onClick, children }) {
const [state, setState] = useState<"idle" | "loading" | "success" | "error">("idle");
async function handleClick() {
setState("loading");
try {
await onClick();
setState("success");
setTimeout(() => setState("idle"), 2000);
} catch {
setState("error");
setTimeout(() => setState("idle"), 1500);
}
}
return (
<motion.button
onClick={handleClick}
disabled={state === "loading"}
animate={{
scale: state === "error" ? [1, 0.97, 1] : 1,
}}
>
<AnimatePresence mode="wait">
{state === "idle" && (
<motion.span key="label"
initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}>
{children}
</motion.span>
)}
{state === "loading" && (
<motion.span key="spinner"
initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}>
<Spinner />
</motion.span>
)}
{state === "success" && (
<motion.span key="check"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0 }}>
✓
</motion.span>
)}
</AnimatePresence>
</motion.button>
);
}
Error Shake
@keyframes errorShake {
0% { transform: translateX(0); }
15% { transform: translateX(-8px); }
30% { transform: translateX(8px); }
45% { transform: translateX(-6px); }
60% { transform: translateX(6px); }
75% { transform: translateX(-4px); }
90% { transform: translateX(4px); }
100% { transform: translateX(0); }
}
.field--error {
animation: errorShake 400ms ease-in-out;
border-color: var(--color-error);
}
In React with Framer Motion:
<motion.input
animate={hasError ? {
x: [0, -8, 8, -6, 6, -4, 4, 0],
transition: { duration: 0.4 }
} : {}}
/>
Success Checkmark Draw (SVG)
function SuccessCheck() {
return (
<motion.svg viewBox="0 0 52 52" width={52} height={52}>
<motion.circle
cx="26" cy="26" r="25" fill="none" stroke="var(--color-success)"
strokeWidth="2"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.4, ease: "easeOut" }}
/>
<motion.path
fill="none" stroke="var(--color-success)"
strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"
d="M14 27 L22 35 L38 19"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.3, ease: "easeOut", delay: 0.3 }}
/>
</motion.svg>
);
}
Toggle — must feel physical, like a real switch:
function Toggle({ checked, onChange }) {
return (
<button
role="switch"
aria-checked={checked}
onClick={() => onChange(!checked)}
className={`toggle ${checked ? "toggle--on" : ""}`}
>
<motion.span
className="toggle__thumb"
layout
transition={{
type: "spring",
stiffness: 500,
damping: 35,
}}
/>
</button>
);
}
.toggle {
width: 44px; height: 24px;
border-radius: 12px;
background: var(--color-surface-muted);
border: none; padding: 2px;
cursor: pointer;
transition: background-color 200ms ease;
}
.toggle--on { background: var(--color-primary); }
.toggle__thumb {
display: block;
width: 20px; height: 20px;
border-radius: 50%;
background: white;
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
Checkbox — the drawing motion implies human confirmation:
function AnimatedCheckbox({ checked, onChange }) {
return (
<label className="checkbox-wrapper">
<input type="checkbox" checked={checked} onChange={onChange} />
<svg viewBox="0 0 24 24" width={24} height={24}>
{/* Box */}
<motion.rect
x="2" y="2" width="20" height="20" rx="4"
fill="none"
stroke={checked ? "var(--color-primary)" : "var(--color-border)"}
strokeWidth="2"
animate={{ fill: checked ? "var(--color-primary)" : "transparent" }}
transition={{ duration: 0.2 }}
/>
{/* Checkmark strokes in */}
{checked && (
<motion.path
d="M6 12 L10 16 L18 8"
fill="none" stroke="white"
strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.2, ease: "easeOut" }}
/>
)}
</svg>
</label>
);
}
Badge appear:
<AnimatePresence>
{count > 0 && (
<motion.span
key="badge"
className="badge"
initial={{ scale: 0 }}
animate={{ scale: [0, 1.2, 1] }}
exit={{ scale: 0 }}
transition={{ type: "spring", stiffness: 500, damping: 30 }}
>
{count}
</motion.span>
)}
</AnimatePresence>
Copy-to-clipboard feedback:
function CopyButton({ text }) {
const [copied, setCopied] = useState(false);
async function copy() {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
return (
<button onClick={copy} aria-label={copied ? "Copied!" : "Copy to clipboard"}>
<AnimatePresence mode="wait">
{copied
? <motion.span key="check" initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }} exit={{ opacity: 0 }}>✓</motion.span>
: <motion.span key="copy" initial={{ opacity: 0 }}
animate={{ opacity: 1 }} exit={{ opacity: 0 }}>⎘</motion.span>
}
</AnimatePresence>
</button>
);
}
Every microinteraction must include:
aria-label on interactive elements describing their state (not just their default action)aria-live="polite" on status messages that appear after async actionsprefers-reduced-motion fallback for all animations@media (prefers-reduced-motion: reduce) {
.skeleton-line, .skeleton-shimmer {
animation: none;
opacity: 0.6; /* Static placeholder — no pulse */
}
}
references/pattern-catalog.md — Extended pattern library: star ratings, progress rings, swipe actions, pull-to-refresh indicators, number counting animations, and 20+ more patterns with full codetesting
Performs quality control on single-cell RNA-seq data (.h5ad or .h5 files) using scverse best practices with MAD-based filtering and comprehensive visualizations. Use when users request QC analysis, filtering low-quality cells, assessing data quality, or following scverse/scanpy best practices for single-cell analysis.
tools
Deep learning for single-cell analysis using scvi-tools. This skill should be used when users need (1) data integration and batch correction with scVI/scANVI, (2) ATAC-seq analysis with PeakVI, (3) CITE-seq multi-modal analysis with totalVI, (4) multiome RNA+ATAC analysis with MultiVI, (5) spatial transcriptomics deconvolution with DestVI, (6) label transfer and reference mapping with scANVI/scArches, (7) RNA velocity with veloVI, or (8) any deep learning-based single-cell method. Triggers include mentions of scVI, scANVI, totalVI, PeakVI, MultiVI, DestVI, veloVI, sysVI, scArches, variational autoencoder, VAE, batch correction, data integration, multi-modal, CITE-seq, multiome, reference mapping, latent space.
testing
This skill should be used when scientists need help with research problem selection, project ideation, troubleshooting stuck projects, or strategic scientific decisions. Use this skill when users ask to pitch a new research idea, work through a project problem, evaluate project risks, plan research strategy, navigate decision trees, or get help choosing what scientific problem to work on. Typical requests include "I have an idea for a project", "I'm stuck on my research", "help me evaluate this project", "what should I work on", or "I need strategic advice about my research".
development
Run nf-core bioinformatics pipelines (rnaseq, sarek, atacseq) on sequencing data. Use when analyzing RNA-seq, WGS/WES, or ATAC-seq data—either local FASTQs or public datasets from GEO/SRA. Triggers on nf-core, Nextflow, FASTQ analysis, variant calling, gene expression, differential expression, GEO reanalysis, GSE/GSM/SRR accessions, or samplesheet creation.