Shader Buttons
NewFreeAccessible buttons whose surface is a small live shader: plasma, embers, a travelling beam, sliding labels, dot-matrix and frosted glass.
- shader
- webgl
- button
- plasma
- glass
- cta
- hover
Installation
pnpm dlx shadcn@latest add https://kagadmodyaa.vercel.app/r/shader-buttons.jsonNeeds a React + Tailwind project set up with shadcn. Copies the effect into your project, along with lib/effect-runtime.ts, the small WebGL layer it runs on. No other dependencies.
Or from npm
pnpm add @kagadmodyaa/effectsimport { ShaderButton } from '@kagadmodyaa/effects/shader-buttons'One package with every free effect, one import path each. No Tailwind or shadcn needed, and no stylesheet to import: the few layout rules an effect needs come with it. The npm package and the command above ship the same source.
Usage
Use it like any other component. Choose a theme with preset; any prop you pass as well overrides the preset.
import { ShaderButton } from '@/components/effects/shader-buttons/shader-buttons'
export function Cta() {
return (
<div className="flex items-center gap-3">
<ShaderButton preset="Plasma" size="lg" onClick={() => signUp()}>
Get started
</ShaderButton>
<ShaderButton variant="glass" label="Book a demo" />
<ShaderButton variant="beam" loading>Saving</ShaderButton>
</div>
)
}
// A normal <button>: type, disabled, ref, aria-* and every event prop work as usual.
// The stage above is <ShaderButtonShowcase />, also exported, if you want the same grid.<ShaderButton preset="Ember" variant={"plasma"} />Presets
PlasmaEmberBeamSlidingDottedGlass
Props
| Name | Type | Default | Description |
|---|---|---|---|
| preset | 'Plasma' | 'Ember' | 'Beam' | 'Sliding' | 'Dotted' | 'Glass' | none (the defaults below) | A named theme. Any prop you pass as well overrides the preset’s value. |
| variant | 'plasma' | 'ember' | 'beam' | 'sliding' | 'dotted' | 'glass' | 'plasma' | Which shader draws the surface. |
| size | 'sm' | 'md' | 'lg' | 'md' | Button size: sm 36px, md 44px, lg 56px tall. |
| label | 'Get started' | 'Start free' | 'Book a demo' | 'Join waitlist' | 'Continue' | 'Get started' | The label, when you do not pass children. Always real text, never part of the shader. |
| tone | 'auto' | 'dark' | 'light' | 'auto' | Which surface the button sits on. auto follows the page theme. |
| colorA | string | '#ff5fa2' | First palette colour. A hex colour. |
| colorB | string | '#5b3dff' | Second palette colour. A hex colour. |
| intensity | number | 1 | How strong the effect is: glow, sparks, highlights. 0 is calm. Range 0 to 1.5. |
| speed | number | 1 | How fast the surface moves. 0 freezes it. Range 0 to 2. |
| radius | number | 32 | Corner radius. At or above half the height it is a pill. Range 0 to 32 px. |
| className | string | — | Extra class on the root element. The effect fills its parent: give the parent a height. |
| style | CSSProperties | — | Inline style on the root element. |
Files
What the command adds to your project, and where.
components/effects/shader-buttons/shader-buttons.tsxThe component.
components/effects/shader-buttons/button.tsxThe component.
components/effects/shader-buttons/surface.tsxThe component.
components/effects/shader-buttons/showcase.tsxThe component.
components/effects/shader-buttons/shaders.tsThe fragment shader, as a string. Edit it to change the picture.
components/effects/shader-buttons/styles.tsThe component.
lib/effect-runtime.tsShared by every shader effect: resizing, pausing, reduced motion, context loss and the no-WebGL fallback. Installed once.
Behaviour
- A real <button>: Enter and Space activate it, Tab focuses it, and a focus-visible ring shows on keyboard focus. The label is ordinary text above the canvas, never part of the shader.
- disabled dims it and freezes the shader; loading shows a spinner, sets aria-busy and ignores clicks while keeping focus.
- The canvas is created only while the button is near the viewport, and at most eight draw at once on a page. Beyond that a button keeps its CSS look.
- It pauses off screen and in a background tab, lowers its resolution automatically if frames run slow, and draws one still frame under prefers-reduced-motion.
- Without WebGL, and before the first frame, you see a CSS version of the same variant in the same colours.
- Tone is 'auto' by default: it follows a dark or light class on <html> (or the OS setting) and re-reads it when the theme changes.