{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "shimmer-button",
  "type": "registry:ui",
  "title": "Shimmer Button",
  "description": "A dark pill CTA with a conic-gradient spark that sweeps around its edge: a rotating cone inside a sliding container, masked by an inset panel that redraws the fill and leaves only the rim glowing.",
  "author": "ofri-peretz <https://github.com/ofri-peretz>",
  "categories": [
    "decorative",
    "effect"
  ],
  "dependencies": [],
  "registryDependencies": [
    "https://ds.interlace.tools/r/theme.json",
    "https://ds.interlace.tools/r/cn.json"
  ],
  "files": [
    {
      "path": "registry/interlace-ui/magicui/shimmer-button.tsx",
      "target": "components/ui/magicui/shimmer-button.tsx",
      "type": "registry:ui",
      "content": "import React, { ComponentPropsWithoutRef, CSSProperties } from \"react\"\n\n\n\n// @interlace/shimmer-button v1.1.0 — Interlace design system.\n// Docs, props and live preview: https://ds.interlace.tools/c/shimmer-button\n// What changed since: https://ds.interlace.tools/c/shimmer-button#history\n// Generated banner — keep it, the upgrade diff reads this version.\n\n/**\n * @interlace/ui — ShimmerButton\n *\n * A dark pill CTA with a conic-gradient spark that sweeps around its edge: a\n * rotating cone inside a sliding container, masked by an inset panel that\n * redraws the fill and leaves only the rim glowing.\n *\n * `shimmer` (the spark) and `highlight` (the inset white glow at the bottom\n * edge) toggle independently, so a secondary CTA can share the geometry\n * without inheriting either effect.\n *\n * Our reimplementation of the Magic UI component of the same name. What is\n * ours: the two effects are separately switchable (so a secondary CTA can keep\n * the geometry and drop the motion, or keep the spark and drop the white inset\n * glow that clashes with non-white fills), and `as` swaps the rendered element\n * so an anchor can wear the same shape.\n *\n * ## Anatomy\n *\n *   ShimmerButton                    (button | as — data-slot=\"shimmer-button\",\n *                                     data-shimmer / data-highlight)\n *     ├─ div[data-shimmer-spark]     (container-type:size, -z-30, blur)\n *     │   └─ div.animate-shimmer-slide\n *     │       └─ div.animate-spin-around   (the conic gradient)\n *     ├─ children\n *     ├─ div[data-shimmer-highlight] (inset bottom-edge box-shadow glow)\n *     └─ div                         (inset:var(--cut) panel that re-paints\n *                                     --bg over the spark, leaving the rim)\n *\n * All tuning arrives as CSS custom properties on the root: `--spread`,\n * `--shimmer-color`, `--radius`, `--speed`, `--cut`, `--bg`, plus\n * `--shimmer-glow` / `--shimmer-glow-strong`, which are `--shimmer-color` at\n * 12% and 25% — the inset highlight is the same light source as the spark, so\n * it is derived rather than being a second colour to keep in sync.\n *\n * ## Motion\n *\n * Pure CSS, covered twice. Both `.animate-shimmer-slide` and\n * `.animate-spin-around` are named in the `prefers-reduced-motion: reduce`\n * block in `styles/tokens.css` (`animation: none !important`) and also caught\n * by the wildcard in `styles/preflight.css`. There is no `useReducedMotion`\n * call and none is needed. Under `reduce` the spark parks in place — it does\n * not vanish — so a static wedge of the conic gradient can remain visible at\n * the rim; pass `shimmer={false}` if you want it gone entirely. The press\n * feedback (`transition-transform … active:translate-y-px`) is a transition,\n * so the preflight duration clamp handles it.\n *\n * ## It is deliberately dark, on the scrim tokens\n *\n * This is the DS's one always-dark button, and `--scrim` / `--scrim-foreground`\n * are the tokens that mean exactly that: black-and-white that does NOT invert\n * per theme, because the surfaces they exist for (image scrims, this button)\n * are dark by intent rather than by mode. Both are `#000000` / `#ffffff` in\n * light and dark alike, so the pair measures 21:1 — SC 1.4.3 has all the room\n * it needs, and the `border-scrim-foreground/10` hairline is decoration, not a\n * 1.4.11 boundary.\n *\n * That is the whole reason the tokenisation is a no-op visually. It was\n * `text-white` / `border-white/10` / `rgba(0, 0, 0, 1)` / `#ffffff` — the same\n * four colours, spelled as literals, which meant a fork of the palette could\n * not reach them and R19 had a hole here.\n *\n * Pass `background` / `shimmerColor` and override the text colour via\n * `className` if you need it to follow the themed palette instead.\n */\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface ShimmerButtonProps extends ComponentPropsWithoutRef<\"button\"> {\n  shimmerColor?: string\n  shimmerSize?: string\n  borderRadius?: string\n  shimmerDuration?: string\n  background?: string\n  className?: string\n  children?: React.ReactNode\n  /**\n   * Render the rotating spark animation (the conic-gradient that sweeps\n   * around the button). Defaults to `true`. Pass `false` to keep the pill\n   * geometry and fill but drop the motion.\n   */\n  shimmer?: boolean\n  /**\n   * Render the inset white highlight at the bottom edge (a `box-shadow:\n   * inset 0 -8px 10px #ffffff1f` glow). Defaults to `true`. Pass `false`\n   * for a darker, flatter look that pairs cleanly with non-white fills.\n   * Independent of `shimmer` — the two effects can be toggled separately\n   * (e.g. shimmer on + highlight off = animated dark sibling).\n   */\n  highlight?: boolean\n  as?: React.ElementType\n}\n\nexport const ShimmerButton = React.forwardRef<\n  HTMLButtonElement,\n  ShimmerButtonProps\n>(\n  (\n    {\n      shimmerColor = \"var(--scrim-foreground)\",\n      shimmerSize = \"0.05em\",\n      shimmerDuration = \"3s\",\n      borderRadius = \"100px\",\n      background = \"var(--scrim)\",\n      shimmer = true,\n      highlight = true,\n      className,\n      children,\n      as: Comp = \"button\",\n      ...props\n    },\n    ref\n  ) => {\n    return (\n      <Comp\n        data-slot=\"shimmer-button\"\n        data-shimmer={shimmer ? \"\" : undefined}\n        data-highlight={highlight ? \"\" : undefined}\n        style={\n          {\n            \"--spread\": \"90deg\",\n            \"--shimmer-color\": shimmerColor,\n            \"--radius\": borderRadius,\n            \"--speed\": shimmerDuration,\n            \"--cut\": shimmerSize,\n            \"--bg\": background,\n            // The inset glow is the same light source as the spark, so it is\n            // derived from `--shimmer-color` rather than being a second colour\n            // to keep in sync. Computed here because Tailwind cannot emit an\n            // arbitrary value it has to evaluate — the class below references\n            // these as plain `var()`, which it can.\n            \"--shimmer-glow\": `color-mix(in oklab, ${shimmerColor} 12%, transparent)`,\n            \"--shimmer-glow-strong\": `color-mix(in oklab, ${shimmerColor} 25%, transparent)`,\n          } as CSSProperties\n        }\n        className={cn(\n          \"group relative z-0 flex cursor-pointer items-center justify-center overflow-hidden [border-radius:var(--radius)] border border-scrim-foreground/10 px-6 py-3 whitespace-nowrap text-scrim-foreground [background:var(--bg)]\",\n          \"transform-gpu transition-transform duration-300 ease-in-out active:translate-y-px\",\n          className\n        )}\n        ref={ref}\n        {...props}\n      >\n        {/* spark container — gated by `shimmer` prop so secondary CTAs can\n            share ShimmerButton's geometry without inheriting its animation */}\n        {shimmer && (\n          <div\n            data-shimmer-spark\n            className={cn(\n              \"-z-30 blur-[2px]\",\n              \"[container-type:size] absolute inset-0 overflow-visible\"\n            )}\n          >\n            {/* spark */}\n            <div className=\"animate-shimmer-slide absolute inset-0 [aspect-ratio:1] h-[100cqh] [border-radius:0] [mask:none]\">\n              {/* spark before */}\n              <div className=\"animate-spin-around absolute -inset-full w-auto [translate:0_0] rotate-0 [background:conic-gradient(from_calc(270deg-(var(--spread)*0.5)),transparent_0,var(--shimmer-color)_var(--spread),transparent_var(--spread))]\" />\n            </div>\n          </div>\n        )}\n        {children}\n\n        {/* Inset white highlight at the bottom edge — gated INDEPENDENTLY\n            of `shimmer` so secondary CTAs can keep the rotating spark while\n            dropping the white inset glow (which clashes with non-white\n            fills like the dark slate secondary). */}\n        {highlight && (\n          <div\n            data-shimmer-highlight\n            className={cn(\n              \"absolute inset-0 size-full\",\n\n              \"rounded-2xl px-4 py-1.5 text-sm font-medium shadow-[inset_0_-8px_10px_var(--shimmer-glow)]\",\n\n              // transition\n              \"transform-gpu transition-all duration-300 ease-in-out\",\n\n              // on hover\n              \"group-hover:shadow-[inset_0_-6px_10px_var(--shimmer-glow-strong)]\",\n\n              // on click\n              \"group-active:shadow-[inset_0_-10px_10px_var(--shimmer-glow-strong)]\"\n            )}\n          />\n        )}\n\n        <div\n          className={cn(\n            \"absolute [inset:var(--cut)] -z-20 [border-radius:var(--radius)] [background:var(--bg)]\"\n          )}\n        />\n      </Comp>\n    )\n  }\n)\n\nShimmerButton.displayName = \"ShimmerButton\"\n"
    }
  ],
  "meta": {
    "tier": "effect",
    "client": false,
    "minViewport": null,
    "loading": false,
    "version": "1.1.0",
    "since": "1.0.0"
  },
  "docs": "## @interlace/shimmer-button\n\nInstalled to `components/ui/magicui/shimmer-button.tsx`.\n\n```tsx\nimport { /* … */ } from '@/components/ui/magicui/shimmer-button';\n```\n\nProps, a11y contract, live preview and source: https://ds.interlace.tools/c/shimmer-button\n\nRequires the `@interlace/theme` CSS baseline (installed automatically as a registry dependency)."
}
