From UI Motion & States
Adds microinteractions to prototypes: skeleton loaders, hover effects, scroll-reveal, and button feedback using CSS and React.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ui-motion:microinteractionsWhen to use
Юзер просит «оживи прототип», «добавь hover», «когда грузится — что показываем», «при клике должно реагировать». В interactive-prototype после статики.
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Маленькие анимации = большая разница в восприятии «это live» vs «это макет».
Маленькие анимации = большая разница в восприятии «это live» vs «это макет».
Серая плашка-плейсхолдер пока грузится контент. Используй вместо спиннера для контента-предсказуемой формы (карточка, строка таблицы, аватар).
@keyframes skel-shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.skel {
background: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 50%, #e5e7eb 75%);
background-size: 200% 100%;
animation: skel-shimmer 1.5s linear infinite;
border-radius: 6px;
}
.skel-line { height: 12px; margin: 6px 0; }
.skel-line.short { width: 40%; }
.skel-line.long { width: 90%; }
.skel-circle { width: 40px; height: 40px; border-radius: 50%; }
{loading ? (
<div style={{ padding: 16 }}>
<div className="skel skel-circle" />
<div className="skel skel-line long" />
<div className="skel skel-line short" />
</div>
) : <RealCard data={data} />}
Стандартный набор для interactive elements:
.btn { transition: background .15s, transform .15s, box-shadow .15s; }
.btn:hover { background: var(--primary-hover); }
.btn:active { transform: translateY(1px); }
.card { transition: transform .2s, box-shadow .2s; }
.card:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.08); }
.link { position: relative; }
.link::after { content: ''; position: absolute; bottom: -2px; left: 0; width: 100%;
height: 1px; background: currentColor; transform: scaleX(0); transform-origin: right;
transition: transform .25s; }
.link:hover::after { transform: scaleX(1); transform-origin: left; }
Правило: hover работает только на устройствах с курсором. Мобильные: используй :active или JS-туч-фидбек.
Появление при скролле, без библиотек:
function Reveal({ children, delay = 0 }) {
const ref = useRef(null);
const [shown, setShown] = useState(false);
useEffect(() => {
const o = new IntersectionObserver(([e]) => {
if (e.isIntersecting) { setShown(true); o.disconnect(); }
}, { threshold: 0.1 });
if (ref.current) o.observe(ref.current);
return () => o.disconnect();
}, []);
return (
<div ref={ref} style={{
opacity: shown ? 1 : 0,
transform: shown ? 'translateY(0)' : 'translateY(20px)',
transition: `opacity .6s ${delay}ms, transform .6s ${delay}ms`,
}}>{children}</div>
);
}
<Reveal> <Hero /></Reveal>
<Reveal delay={100}><Features /></Reveal>
<Reveal delay={200}><Pricing /></Reveal>
.ripple { position: relative; overflow: hidden; }
.ripple::after {
content: ''; position: absolute; inset: 0;
background: radial-gradient(circle, rgba(255,255,255,0.3) 1px, transparent 60%);
background-size: 0 0; background-position: var(--rx, 50%) var(--ry, 50%);
transition: background-size .5s;
}
.ripple:active::after { background-size: 200% 200%; }
<button className="ripple" onMouseDown={(e) => {
const r = e.currentTarget.getBoundingClientRect();
e.currentTarget.style.setProperty('--rx', `${e.clientX - r.left}px`);
e.currentTarget.style.setProperty('--ry', `${e.clientY - r.top}px`);
}}>Click</button>
Цифры «крутятся» к финальному значению:
function Tick({ value, duration = 1000 }) {
const [n, setN] = useState(0);
useEffect(() => {
const start = Date.now();
const id = setInterval(() => {
const t = Math.min(1, (Date.now() - start) / duration);
setN(Math.round(value * (1 - Math.pow(1 - t, 3)))); // easeOut cubic
if (t === 1) clearInterval(id);
}, 16);
return () => clearInterval(id);
}, [value]);
return <span>{n.toLocaleString()}</span>;
}
@keyframes pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4); }
50% { box-shadow: 0 0 0 12px rgba(59, 130, 246, 0); }
}
.pulse { animation: pulse 2s infinite; }
Используй на одном CTA, не на каждой кнопке.
Уважай accessibility:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0s !important;
transition-duration: 0s !important;
}
}
prefers-reduced-motion → ломаешь a11yGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.
npx claudepluginhub jhamidun/claude-code-config-pack --plugin ui-motion