Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Shaders

Pro

Guilloche Shader for React

Banknote engraving drawn on the GPU: hair-thin interlaced lines weave a rosette and border bands, gold on emerald, under a gliding sheen. For React with Base UI, when a fintech, payments or premium page wants the look of security printing.

Framework

Guilloche Shader draws guilloché, the engraving on banknotes, share certificates and watch dials. Two families of petal-shaped rings twist opposite ways around a large rosette on the right, so their hairlines cross into a woven net, with a finer ring around the edge and wavy bands of interlaced lines along the top and bottom. Everything is gold on deep emerald. The two families turn slowly against each other, so the weave seems to rotate, and a soft sheen glides across on the diagonal like light over foil. A WGSL fragment shader on WebGPU draws each line as an anti-aliased hairline about one CSS pixel wide.

Use it behind a fintech or payments hero, a gift card or voucher, or a premium pricing tier. The left of the box stays plain emerald, so a headline sits there with no panel. For black-and-white beats between gratings, use Moire Shader; for a never-repeating lattice of rosettes in color, Quasicrystal Shader. It runs only while it's on screen, holds a still frame with reduced motion, and shows faint CSS rings on the same emerald 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 { GuillocheShader } from "@/components/ui/guilloche-shader";
<GuillocheShader className="flex min-h-[32rem] items-center">
  <h1 className="max-w-[30%] px-10 text-5xl font-semibold text-white">
    Money, made beautiful
  </h1>
</GuillocheShader>

Guilloche Shader is a container: your content sits on top of the engraving, 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 rosette sits right of center, at 62% of the width, and the plain emerald to its left, between the bands, keeps white text above 11:1. Over the lines, put copy on a bg-black/60 panel, which keeps white text above 6.5:1.

Demos

Voucher

The engraving scales with its box, here a gift card at a 1.75 aspect ratio. groundColor and lineColor recolor it, plum and rose, and petals={8} gives the rosette fewer, broader waves. density={40} thins the lines to suit the small surface.

Preview width

Performance

About 0.2 ms of GPU time a frame at 1280×720 on an M2 Max laptop. Keep quality at 1: the lines are hairlines 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 faint gold rings around the rosette's spot on the same emerald, 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 engraving is decoration: it's hidden from assistive tech and lets every click through to your content. With reduced motion it draws one still frame of the rosette and the bands, and stops. Nothing flashes: the weave turns over tens of seconds, and the sheen takes about 14 seconds to cross. White text reads above 11:1 on the plain emerald left of the rosette; over the lines, use a bg-black/60 panel, or one of the ground color at 80% (bg-[#0d3b2e]/80): both keep white text above 6.5:1.

FAQ

How are the lines drawn?

Each family is a field, the radius times a wave around the rosette, r (1 + a sin(kθ + twist)), and every line is one of its contours, drawn as an anti-aliased hairline. Two families twisting opposite ways cross into the woven net, a third with twice the waves fills the outer ring, and the bands are the same waves laid flat. Where lines crowd closer than a few pixels they blend to their average tone, so they never shimmer.

How do I change how fine the engraving is?

density is how many lines each family draws from the rosette's center to its edge, from 20 to 140. The shader keeps the lines at least 3.5 CSS pixels apart on average, so in a small box you get fewer lines than you ask for, and the engraving stays legible.

How do I change the rosette's shape?

petals is how many waves each line makes around the rosette, from 5 to 24. 6 to 8 gives a few broad, flower-like lobes; the default 12 a fine star; 20 and up a dense, lacy ring. The border bands follow it, with half as many waves.

Can I change the colors?

Yes: lineColor is the engraving, groundColor the paper under it, and sheenColor the tint the lines take as the sheen passes. They take any CSS color, var(--primary) included, and follow a theme switch. Keep the lines lighter than the ground: the sheen brightens them. intensity sets how strong the sheen is, from 0 (none) to 2.

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.

What happens in browsers without WebGPU?

The box keeps its CSS still: faint gold rings around a soft emerald glow where the rosette would be, so the page still looks finished. Nothing errors: the root's data-state reads unsupported.

API Reference

GuillocheShader

data-slot="guilloche-shader"

PropType
density?number

How many lines each family draws across the rosette's radius, from 20 to 140. @default 60.

Default
60
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.

groundColor?string

The ground under the engraving. Any CSS colour. @default "#0d3b2e".

Default
"#0d3b2e"
intensity?number

How bright the gliding sheen is, from 0 (none) to 2. @default 1.

Default
1
interactive?boolean

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

lineColor?string

The engraved lines. Any CSS colour. @default "#e3c77d".

Default
"#e3c77d"
paused?boolean

Holds the current frame. @default false.

petals?number

How many waves the rosette's lines make around it, from 5 to 24. @default 12.

Default
12
quality?number

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

sheenColor?string

The colour the lines take under the sheen. Any CSS colour. @default "#fff3cf".

Default
"#fff3cf"
speed?number

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

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