Skip to content

Skill · 04 of 09

components take the idea, not the code

Source animated and "fancy" UI from component libraries safely, then ship it as lean void code.

skills/components/SKILL.md197 lines4 referencesupdated 16 Sept 2026

When Claude Code loads it

Use when adding animated or fancy UI effects, hero backgrounds (aurora, grid, dots, beams, spotlight, shader), text effects (split/blur/shiny/typewriter reveal), marquees and logo walls, bento or spotlight cards, border beams, number tickers/counters, magnetic buttons, or when the user mentions or pastes code from React Bits, reactbits.dev, skiper-ui, Magic UI, Aceternity, shadcn registries, `npx shadcn add @…`, 21st.dev, motion-primitives, Animate UI, Cult UI, Kokonut, Eldora, smoothui, uiverse, Hover.dev, coss/Origin UI, Tailark or kibo-ui. Also use when choosing Base UI vs Radix vs React Aria for dialogs, drawers, toasts and comboboxes.

Component libraries are catalogues of techniques, not dependencies. The output is always void-native: tokens only, a Server Component where possible, the lowest effect tier that carries the look, audited, and attributed.

Owner of: library sourcing, licences, registry audits, and signature-effect translations. For UI micro-interactions (press, dropdown, dialog, toast, accordion, tabs), timing and effect tiers, see motion. For WebGL and canvas hooks, see speedreferences/rendering-smoothness.md §8 and motionreferences/webgl-canvas.md. For rebuilding a site you admire, see inspire. For the visual system and anti-slop checks, see craft.

The 10 rules

  1. Check the licence before you read the code. MIT/Apache/Unlicense can be vendored with the notice kept. React Bits, Animate UI, Aceternity, Hover.dev and skiper-ui (free tier) are inspiration only in anything open-source: write a clean-room rewrite. React Bits' Commons Clause explicitly bans redistributing "a ported version".
  2. Effect budget: 1 signature + 1 secondary per page. A signature is a hero backdrop, a hero product animation, or one scroll set piece. A secondary is one marquee, one spotlight grid or one beam card. Masked patterns, one glow and grain are free. Zero decorative effects on authenticated app routes, and none on anything a user triggers 100+ times a day or from the keyboard.
  3. Behaviour comes from a headless library, visuals from tokens. Dialogs, drawers, menus, comboboxes, tabs and toasts come from Base UI / Radix / React Aria (+ sonner, cmdk). Never ship a registry's hand-rolled div modal.
  4. Use a recipe before a registry. Check references/recipes.md first. Adapt registry source only when no recipe or library covers the effect, and only after the audit.
  5. Never npx shadcn add straight into the app while evaluating. Fetch the JSON, read files[].content, dependencies and css/cssVars, and run the audit. Unaudited registry code does not ship.
  6. Assume every registry component is broken until proven otherwise: no reduced motion (Magic UI 5/78, Aceternity 3/118, React Bits 3/56 backgrounds), no offscreen pause, hardcoded colours, v3-only Tailwind. Catalogue of failures: references/upstream-bugs.md.
  7. The server HTML carries the real content. Headlines, numbers and links are in the HTML and visible without JS. Never use opacity:0 on LCP text, and never let a counter SSR as 0.
  8. Compositor-first. Continuous motion uses only transform/translate/opacity. Zero React renders per frame. Every loop pauses offscreen and has a reduced-motion finished frame.
  9. One of each: one animation runtime (motion, never also framer-motion or GSAP), one WebGL context per page, one primitive library per app.
  10. Attribute. Vendored MIT code keeps its copyright header plus a THIRD_PARTY_NOTICES.md entry. A clean-room rewrite gets an "inspired by" comment. Never claim affiliation.

Decision tree

text
Need a component or effect?
├─ Behaviour (focus trap, menu, combobox, drawer, toast, ⌘K, date picker)?
│    → headless library + token styling:
│      Base UI (@base-ui/react 1.8: Dialog, Drawer, Toast, Combobox, OTPField) · Radix (radix-ui) ·
│      React Aria (i18n, dates, drag a11y) · sonner (toasts) · cmdk (⌘K, no open animation).
│      vaul is unmaintained → Base UI Drawer. Motion for these: `motion` recipes.
├─ Static structure (pricing table, bento, footer, auth form)?
│    → build from template primitives (Card, Section, Button). For layout ideas, read MIT sources
│      (shadcn blocks, Tailark GitHub, coss ui, opensourceui). No animation needed.
├─ Signature/secondary visual (marquee, spotlight, reveal, beam, aurora, ticker…)?
│    1. GATE: frequency (first visit only), a purpose in one word, budget left on this page? No → T0 static.
│    2. RECIPE: is it in references/recipes.md? → paste it. Done.
│    3. LIBRARY: a real package that solves it? numbers → @number-flow/react · globe → cobe (T4) ·
│       text morph → torph · springs/layout/exit → motion (LazyMotion + m).
│    4. REGISTRY (last resort): shortlist 2–4 by licence + tier → fetch JSON → 16-point audit →
│       MIT-class: vendor + fix every fail · anything else: write a spec, close the source, clean-room.
│       Tier too high for the budget (WebGL for a mood)? → down-tier (inspire → effect-downgrades).
└─ User pasted code from a library? → identify licence + component → run the audit → rewrite.

Effect tiers (summary; the full rules are in motion §3)

Tier What JS Page budget
T0 static CSS gradients, masks, grid/dots, grain, glow 0 KB free within taste: one glow, one grain, masked patterns
T1 CSS animated keyframes, transitions, @starting-style, view() timelines 0 KB ≤3 infinite per viewport, ≤1 paint-bound
T2 JS / motion pointer-driven, springs, counters rAF 0.3–1 KB · LazyMotion +32 KB · full motion +46 KB 1 hero signature + 1 secondary; ≤2 islands
T3 canvas 2D particle/line fields 2–12 KB 1 viewport canvas; excludes T4
T4 WebGL shaders, globes, 3D ogl 10–14 · cobe 5 · three 140+ KB 1 context, marketing hero only

Assign a tier from a registry item's dependencies: nothing or CSS → T1 · motion/framer-motion/gsap/rAF → T2 · getContext('2d') → T3 · ogl/three/@react-three/*/cobe/webgl → T4.

Library Licence Install in an app Copy into an OSS repo / template Attribution
shadcn/ui, Magic UI, motion-primitives, Cult UI, kibo-ui, smoothui, Kokonut, Eldora, Luxe, RareUI, opensourceui, uiverse (galaxy) MIT yes yes, keep notice licence text in notices
coss ui / Origin UI (coss.com/ui/r, coss.com/origin/r) MIT (repo default AGPL; apps/ui + apps/origin MIT) yes registry components yes; never the AGPL app code notice
Tailark MIT on GitHub; hosted /r/ blocks paywalled (401) from GitHub from GitHub notice
Base UI, Radix, Ark UI, sonner, cmdk, vaul MIT depend depend
React Aria Apache-2.0 depend keep NOTICE NOTICE
GSAP (all plugins, incl. SplitText) free standard licence (no Webflow-like builders) yes, but ~25–70 KB and a second runtime depend only
ogl Unlicense yes yes
React Bits MIT + Commons Clause (no selling or redistribution, "or as a ported version") yes, via reactbits.dev no: clean-room only courtesy
Animate UI MIT + Commons Clause ("in their original form") yes clean-room courtesy
Skiper UI free / Pro free: use and modify with attribution required; Pro paid free with its licence comment kept clean-room (else every user inherits attribution) required if used
Aceternity UI proprietary terms: no redistribution of item or source copy free items into the app clean-room courtesy
Hover.dev proprietary per purchase nothing
21st.dev ToS 2026-07-20: use via the platform; per-author licences per author's licence, via official MCP/CLI design reference only credit the author
shadcnblocks (paid), Skiper Pro, Aceternity Pro commercial buyers only, never in public repos nothing

Ideas and techniques are free to reuse; the source text is not. Details and per-library notes: references/catalogue.md.

Fetch registry source (read-only; nothing is written to the repo)

bash
curl -sL https://magicui.design/r/marquee.json | jq -r '.files[].content'          # source
curl -sL https://magicui.design/r/marquee.json | jq '{dependencies, registryDependencies, css, cssVars}'
npx shadcn@latest view @magicui/marquee                                             # via the 344-namespace index
curl -sL https://reactbits.dev/r/Aurora-TS-TW.json | jq -r '.files[0].content'      # <Name>-<TS|JS>-<TW|CSS>
curl -sL https://ui.aceternity.com/registry/spotlight.json | jq -r '.files[0].content'
curl -sL https://skiper-ui.com/registry/skiper40.json | jq -r '.files[0].content'   # new path; /r/skiperN.json still 200
curl -sL https://coss.com/ui/r/button.json | jq -r '.files[0].content'
curl -sL https://ui.shadcn.com/r/registries.json | jq -r '.[] | select(.name=="@smoothui") | .url'  # find any URL template
# 429 bot challenge (motion-primitives, Cult UI) → raw GitHub:
curl -sL https://raw.githubusercontent.com/ibelick/motion-primitives/main/public/c/magnetic.json | jq -r '.files[0].content'
curl -sL https://raw.githubusercontent.com/nolly-studio/cult-ui/main/apps/www/public/r/texture-card.json | jq -r '.files[0].content'
# no registry (opensourceui) → raw file:
curl -sL https://raw.githubusercontent.com/bidyut10/opensourceui/main/components/<cat>/<name>.tsx
  • 401/403/Missing license key means paid or authenticated. Stop. Don't look for mirrors.
  • RareUI slugs differ from its docs (/components/fluidorb/r/fluid-orb.json).
  • 21st.dev /r/* returns 403 without auth. Use the official @21st-dev/magic MCP with the user's key, and treat results as reference.
  • Record the dependencies. A custom animate-* class with no css/cssVars in the item means the animation is dead on Tailwind v4 (Aceternity ships none).

Audit (16 points; full grep hints in references/audit-checklist.md)

Each fail becomes a line in the rewrite plan.

  1. Licence: the class from the matrix plus the actual file header.
  2. SSR safety: no window/document/navigator/matchMedia in render or at module scope; no Math.random()/Date.now() in render (lint/browser-global-in-render).
  3. Server HTML complete: headline, numbers and links present and visible; no opacity-0/initial="hidden" on text.
  4. Cleanup 1:1: every addEventListener/observe/rAF/setInterval/.on(/new Lenis/createGlobe has a remove, disconnect, cancel, clear, off or destroy (lint/listener-without-cleanup, lint/raf-without-cancel, lint/timer-in-render).
  5. Hooks: none inside conditionals or JSX; no m.create() in render; no ref.current in deps; no MotionValue .on in render (lint/motionvalue-subscribe-in-render).
  6. Zero per-frame React work: no setState in pointer/scroll/rAF handlers; no allocation (new Intl.NumberFormat, new Color) in loops.
  7. Reduced motion: a finished static frame with the content intact (lint/no-reduced-motion, smooth/reduced-motion-ignored).
  8. Offscreen pause: infinite animations use data-fx or IntersectionObserver; JS loops also stop on visibilitychange (smooth/raf-loop-idle).
  9. DPR cap ≤1.5 (1 for backdrops); full-bleed shader render scale ≤0.6.
  10. FPS cap ≤30 for ambient T3/T4.
  11. Properties: continuous animation only on transform/opacity; no width/height/top/left, gradient or filter loops (lint/animate-layout-prop, smooth/animate-layout-property).
  12. A11y: decorative layers aria-hidden; duplicates aria-hidden + inert; real /
  13. Hover gating: (hover:hover) and (pointer:fine); pointer handlers check pointerType.
  14. Weight and duplicates: fits the tier; no framer-motion + motion, no @base-ui-components/react + @base-ui/react, no three for a 2D quad, no face-api (lint/heavy-import).
  15. Tokens: no hex/rgb/neutral-800/purple-500; motion uses --ease-*/--duration-* (lint/hardcoded-colors, design/purple-gradient, design/gradient-text).
  16. Tailwind v4: no bg-gradient-to-*, flex-shrink-0, duration-[--x], config keyframes or undefined animate-* (lint/tw-v3-arbitrary-var, lint/tw-v3-renamed, lint/tw-unknown-class, lint/tw-js-config-ignored).

Rewrite onto void tokens

Clean-room procedure (required for non-MIT sources, recommended for all):

  1. Write a spec of 5–10 lines: what moves, which property, duration, trigger, and the reduced-motion frame.
  2. Close the source.
  3. Build at the lowest tier, reusing recipe patterns.
  4. Diff check: no identifiers, comments, magic numbers or file structure carried over.
Registry code void
bg-black, bg-neutral-950, dark:bg-black bg-bg (page) · bg-surface / surface-card (cards)
text-white, text-black dark:text-white · text-neutral-400 text-fg · text-fg-muted (text-fg-subtle for meta/captions/placeholders, AA 4.5:1; text-fg-faint only for disabled controls or aria-hidden decoration, 3:1, never readable text)
border-neutral-800, border-white/10 border-line (border-line-strong on hover)
#9c40ff → #ffaa40, from-purple-500 to-pink-500 one hue: var(--brand), color-mix(in oklab, var(--brand) 20%, transparent), --brand-line
shadcn bg-background text-muted-foreground bg-primary bg-accent bg-bg text-fg-muted bg-brand bg-surface-hover, or import @void/tokens/shadcn.css (shadcn accent = hover surface, not brand)
shadow-lg, rounded-3xl shadow-2 (default shadows are removed), rounded-xl
transition-all duration-500 ease-in-out transition-[translate,opacity] duration-(--duration-slow) ease-out
bg-gradient-to-r, flex-shrink-0, duration-[--x] bg-linear-to-r, shrink-0, duration-(--x)
animate-spotlight with no CSS; tailwind.config keyframes @keyframes in plain CSS inside @layer components, or --animate-* + @keyframes in @theme
import { motion } from "framer-motion" CSS first; else import * as m from "motion/react-m" + LazyMotion
useState position on mousemove a ref + rAF writing el.style.translate (recipe 2)
missing 'use client' (all React Bits files) add it only on the smallest interactive leaf; keep the rest server

Recipes (references/recipes.md, all verified in a Next 16 build)

Component Replaces Tier / JS
Marquee Magic UI marquee, React Bits LogoLoop T1 / 0
SpotlightCard React Bits SpotlightCard, Magic UI magic-card T2 / ~0.5 KB
RevealText React Bits SplitText/BlurText, Aceternity text-generate-effect T1 / 0
BeamBorder Magic UI border-beam, React Bits StarBorder T1 / 0
PatternBackdrop (+ grain) Magic UI grid/dot-pattern, Aceternity background-beams T0 / 0
NumberTicker Magic UI number-ticker, React Bits CountUp T2 / ~0.6 KB
Magnetic React Bits Magnet, motion-primitives magnetic T2 / ~0.5 KB
ShinyText React Bits ShinyText, Magic UI animated-shiny-text T1-paint / 0
AuroraBackdrop React Bits Aurora/Silk (WebGL) T1 / 0
HeroSpotlight Aceternity spotlight T1 one-shot / 0

Shared infrastructure: FxGate (mount once, pauses every [data-fx] offscreen) and src/styles/fx.css imported last in globals.css. Two measured traps: never put a paint animation (ShinyText) over an animated backdrop, and never fade an animated layer with mask-image (use a static overlay).

PatternBackdrop/AuroraBackdrop/HeroSpotlight above cover T0/T1 section backgrounds. For the rest of the "React Bits Backgrounds" vocabulary the owner points to (liquid/fluid distortion, particle field, pixel/dither grid, displacement grid) — a measured decision table by cost tier, the cheap-first CSS/SVG ladder to try before any of them, the one-shared-WebGL-context pattern for a page with several background sections, and paste-ready clean-room OGL implementations: motion/references/backgrounds.md. Same rule 1 applies: these are the idea, rewritten from scratch — never a ported React Bits/Animate UI file.

Attribution

ts
/**
 * Adapted from Magic UI "marquee": https://magicui.design/docs/components/marquee
 * Copyright (c) Magic UI. MIT License (see THIRD_PARTY_NOTICES.md).
 * Changes: CSS-only, aria-hidden/inert duplicate, offscreen pause, reduced motion, void tokens.
 */
ts
/**
 * Visual idea inspired by React Bits "SpotlightCard" (https://reactbits.dev).
 * Independent implementation; no source code copied.
 */
  • Vendored MIT: keep the header, and append the full licence text to THIRD_PARTY_NOTICES.md (create it at the repo root).
  • Skiper free code actually used: keep its entire trailing licence comment. Prefer clean-room.
  • 21st.dev: credit the component author and follow that author's licence.

Verify

  1. void lint: 0 errors, and no lint/hardcoded-colors, lint/no-reduced-motion, lint/raf-without-cancel or lint/tw-* in the new files.
  2. next build; then curl -s localhost:3000/ | grep '12,400' (or the real headline and number) proves server HTML.
  3. void smooth --start "next start -p 3000" --port 3000: dropped frames ≤5% while scrolling, and the WebGL context count ≤1.
  4. Screenshot dark, light, --force-prefers-reduced-motion and 390px (see audit); every effect shows a finished, on-token frame.

References