Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Shaders

Pro

Quasicrystal Shader for React

A never-repeating lattice of rosettes drawn on the GPU, banded from deep teal to gold, that morphs as it breathes. For React with Base UI, when a science, maths or protocol page wants order that never quite repeats.

Framework

Quasicrystal Shader adds up seven plane waves at evenly spaced angles. Where their crests meet they build rosettes, and because the angles don't fit any grid, the lattice never repeats: each rosette is a little different from the next, like a mosaic floor laid by hand. The waves' phases advance together, so the rosettes grow, split and fold into new ones, and a slow zoom breathes in and out. A WGSL fragment shader on WebGPU cuts the field into flat bands, from deep teal through teal to gold, with a thin bright line on every step.

Use it behind a maths or science hero, a protocol or research page, or anywhere that wants pattern with structure rather than noise. For free-flowing sine color with no symmetry, use Plasma Shader; for an aperiodic tiling of real tiles, Penrose Tiling Shader; for black-and-white beats between gratings, Moire Shader. It runs only while it's on screen, holds a still frame with reduced motion, and shows a CSS rosette in the same colors in browsers without WebGPU.

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Usage

import {
  QuasicrystalShader,
} from "@/components/ui/quasicrystal-shader";
<QuasicrystalShader className="flex min-h-[32rem] items-center justify-center">
  <div className="rounded-xl bg-black/60 px-8 py-6">
    <h1 className="text-5xl font-semibold text-white">Ship faster</h1>
  </div>
</QuasicrystalShader>

Quasicrystal Shader is a container: your content sits on top of the lattice, in normal flow. It fills its own box, so give it a height or content. For a backdrop behind the whole page, render it empty with className="fixed inset-0 -z-10". The deep teal takes most of the box, but the gold peaks are bright, so white text needs a panel: bg-black/60 keeps it above 6.8:1 everywhere.

Demos

Fivefold

symmetry={5} uses five waves, so the rosettes are bolder and pentagonal, with more space between them. deepColor, midColor and lightColor recolor the ramp, here night indigo to ice blue, for a protocol or crypto page.

Preview width

In a card

bands={0} turns off the bands and their lines, so the field blends smoothly from deep to gold, like soft focus on the same lattice: a quiet texture for a card. The pattern scales with the box's height, so the rosettes stay the same count in a card as in a hero.

Preview width

Performance

About 0.2 ms of GPU time a frame at 1280×720 on an M2 Max laptop with the default 7 waves; each wave is one cosine and one sine per pixel, so symmetry={12} costs a little more. Keep quality at 1: the band lines are about a pixel wide and go soft at half resolution, and the cost is already small. The animation stops when the shader scrolls out of view, and the browser pauses it in a hidden tab. Every shader on a page shares one GPU device and one frame loop, and vgpu (about 50 KB gzipped) loads only when the first one mounts.

Browser support

The shader needs WebGPU: Chrome and Edge, Safari 26 and later, and Firefox on Windows and macOS. Elsewhere, and until the first frame, the box shows a CSS rosette of teal spokes around a gold glow, so the layout never jumps. data-state on the root reads unsupported there, if you want to style around it. See Shader Canvas.

Accessibility

The lattice is decoration: it's hidden from assistive tech and lets every click through to your content. With reduced motion it draws one still frame, a ringed rosette at the center of the lattice, and stops. Nothing flashes: the rosettes morph over about 12 seconds a cycle. The gold peaks are bright, so never set text on the lattice bare: a bg-black/60 panel keeps white text above 6.8:1, and a panel of the deep color at 80% (bg-[#06262b]/80) above 9:1.

FAQ

How is the pattern made?

It's a sum of plane waves, cosines running in evenly spaced directions. With 5, 7 or more directions there's no grid that fits them all, so the sum is quasiperiodic: ordered, with rosettes everywhere, but never the same twice. Advancing every wave's phase together makes the rosettes morph, and the field is then cut into flat bands.

What does symmetry change?

symmetry is how many waves meet, from 5 to 12. Five gives bold, pentagonal rosettes; 7 the default mosaic; 10 to 12 a large ringed medallion in the middle, framed by finer, lacy rosettes.

How do I get a smooth look instead of bands?

bands={0} blends the colors smoothly and drops the lines. From 2 to 12, bands sets how many flat steps the ramp is cut into: 3 is a bold, poster look, and 12 a fine contour map. intensity sets the contrast: lower keeps more of the box in the middle color, higher pushes it to deep and gold.

Can I change the colors?

Yes: deepColor is the low ground, midColor the middle of the ramp, and lightColor the peaks and the lines between bands. They take any CSS color, var(--primary) included, and follow a theme switch. Keep them ordered dark to light, or the rosettes read inside out.

How do I make the rosettes bigger?

scale sizes the lattice, from 0.3 to 3: 2 doubles every rosette, 0.5 halves it. At 1 the box's height holds about eight wavelengths, so the pattern looks the same in a card and in a hero.

Will it slow my page down?

No: it costs about 0.2 ms of GPU time a frame at 1280×720 on a laptop, and the CPU only sets a few numbers each frame. It stops when it's off screen.

API Reference

QuasicrystalShader

data-slot="quasicrystal-shader"

PropType
bands?number

How many flat bands the field is cut into, from 2 to 12; 0 is a smooth blend with no lines. @default 6.

Default
6
deepColor?string

The low ground. Any CSS colour. @default "#06262b".

Default
"#06262b"
fallback?string

What shows until the first frame and in browsers without WebGPU: a CSS background value (gradients, sizes, a colour) that looks like the shader. It can use the colour props' --shader-<field> variables.

intensity?number

Contrast, from 0 (flat) to 2 (more ground in the deep and light colours). @default 1.

Default
1
interactive?boolean

Whether u.pointer follows the pointer. @default false.

lightColor?string

The rosette peaks and the lines. Any CSS colour. @default "#f2c46d".

Default
"#f2c46d"
midColor?string

The middle of the ramp. Any CSS colour. @default "#1f8a8a".

Default
"#1f8a8a"
paused?boolean

Holds the current frame. @default false.

quality?number

Render scale, from 0.25 to 1, of the screen's pixels (capped at 2×). @default 1.

scale?number

How large the rosettes are, from 0.3 to 3: 2 doubles them. @default 1.

Default
1
speed?number

Animation speed: 1 is normal, 0 holds the frame. @default 1.

symmetry?number

How many plane waves meet at evenly spaced angles, from 5 to 12: more gives finer, rounder rosettes. @default 7.

Default
7
time?number

A fixed moment, in seconds: draws it and doesn't animate.

render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Forwarded to the part.

All 101 shaders