← каталог
Shimmer Button
Кнопка с бегущим бликом: по кайме крутится коническая подсветка (@property), по плашке раз в цикл проходит зайчик. Оба слоя — псевдоэлементы, кнопка остаётся кнопкой.
csshoverrevealintermediate~2 КБreduced-motion ✓
🎛 Настройки — крути и копируй
Пропсы
Разобрано из кода записи: обязательные — без «?», у остальных показано значение по умолчанию.
| Проп | Тип | По умолчанию | Что это |
|---|---|---|---|
| children? | ReactNode | "Начать бесплатно" | |
| shine? | string | "#ffffff" | Цвет блика (формат «#ffffff»). |
| duration? | number | 2.6 | Длительность пробега блика, сек (формат «2.6»). |
| onClick? | () => void; | — |
Установка
npx shadcn@latest add https://motiva.pages.dev/r/shimmer-button.jsonЧто нужно в проекте до установки
components.jsonв корне — с алиасами в форме"components": "@/components". Нет файла — CLI не поймёт, куда класть.- Алиас
@объявлен дважды:"paths": {"@/*": ["./src/*"]}в корневомtsconfig.json(в Vite он часто содержит толькоreferences— тогда CLI создаёт папку@) иresolve.aliasв конфиге сборщика. - Токены подключены один раз на приложение:
import "@/styles/motiva-tokens.css"— они приезжают вместе с записью. Без них секция потеряет цвета, ритм и тему. - Своя типографика не должна спорить: если у вас на обёртке
text-align: center, он протечёт внутрь блоков. - Проект без TypeScript? Всё равно нужен минимальный
tsconfig.jsonсpathsи"tsx": trueвcomponents.json: CLI грузит tsconfig безусловно, а с"tsx": falseидёт заjsconfig.jsonи падает так же. Приехавший рядом.tsxможно не трогать — вам нужен.vanilla.js.
Экспорт
Код
"use client";
import type { CSSProperties, ReactNode } from "react";
export interface ShimmerButtonProps {
children?: ReactNode;
/** Цвет блика (формат «#ffffff»). */
shine?: string;
/** Длительность пробега блика, сек (формат «2.6»). */
duration?: number;
onClick?: () => void;
}
/**
* Кнопка с бегущим бликом: по границе крутится коническая подсветка (@property + анимируемый
* угол), а по самой плашке раз в цикл пробегает диагональный «солнечный зайчик».
* Оба слоя — псевдоэлементы, поэтому кнопка остаётся обычной кнопкой: контент не обёрнут,
* фокус и семантика на месте. Уважает prefers-reduced-motion.
*/
export function ShimmerButton({
children = "Начать бесплатно",
shine = "#ffffff",
duration = 2.6,
onClick,
}: ShimmerButtonProps) {
return (
<>
<style>{`
@property --shb-angle { syntax: "<angle>"; initial-value: 0deg; inherits: false; }
@keyframes shb-spin { to { --shb-angle: 360deg; } }
@keyframes shb-sweep { 0% { transform: translateX(-140%) skewX(-18deg); } 55%, 100% { transform: translateX(240%) skewX(-18deg); } }
.shb {
position: relative; isolation: isolate; overflow: hidden;
padding: 13px 26px; border: none; border-radius: 999px; cursor: pointer;
font: 600 0.95rem/1 var(--ds-font, system-ui, sans-serif); letter-spacing: -0.01em;
/* плашка и текст — ПАРА токенов, а не два независимых цвета. Раньше здесь стоял
тёмный фон и var(--ds-fg): в тёмной теме читалось, в светлой --ds-fg темнеет,
и надпись пропадала на тёмной плашке. Акцентная пара контрастна в обеих. */
color: var(--ds-accent-fg, oklch(0.14 0.02 275));
background: var(--ds-accent, oklch(0.72 0.16 300));
transition: transform 0.18s var(--ds-ease-spring, cubic-bezier(0.34, 1.56, 0.64, 1)), box-shadow 0.25s;
box-shadow: 0 6px 22px var(--ds-accent-ring, oklch(0.5 0.2 295 / 0.25));
}
.shb:hover { transform: translateY(-1px); background: var(--ds-accent-hover, oklch(0.78 0.16 300));
box-shadow: 0 10px 30px var(--ds-accent-ring, oklch(0.5 0.2 295 / 0.4)); }
.shb:active { transform: translateY(0) scale(0.985); }
.shb:focus-visible { outline: 2px solid var(--ds-accent, oklch(0.75 0.16 300)); outline-offset: 3px; }
/* вращающаяся кайма: конический градиент под маской-рамкой */
.shb::before {
content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1.5px;
background: conic-gradient(from var(--shb-angle),
transparent 0deg, var(--shb-shine) 40deg, transparent 90deg,
transparent 180deg, var(--shb-shine) 220deg, transparent 270deg);
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor; mask-composite: exclude;
animation: shb-spin var(--shb-dur) linear infinite;
z-index: -1;
}
/* диагональный зайчик по плашке */
.shb::after {
content: ""; position: absolute; top: -20%; left: 0; width: 45%; height: 140%;
background: linear-gradient(90deg, transparent, var(--shb-shine), transparent);
opacity: 0.32; filter: blur(6px); pointer-events: none;
animation: shb-sweep var(--shb-dur) cubic-bezier(0.5, 0, 0.2, 1) infinite;
}
@media (prefers-reduced-motion: reduce) {
.shb::before, .shb::after { animation: none; }
.shb::after { opacity: 0; }
}
html[data-shot] .shb::before, html[data-shot] .shb::after { animation: none; }
`}</style>
<button
className="shb"
onClick={onClick}
style={{ "--shb-shine": shine, "--shb-dur": `${duration}s` } as CSSProperties}
>
{children}
</button>
</>
);
}
Похожие эффекты
лицензия: MITавтор: Motivaисточник: оригинал (Motiva)v1.0.0