shadcn.io is not affiliated with official shadcn/ui
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.
Installation
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.
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.
API Reference
GuillocheShader
data-slot="guilloche-shader"
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.
Related
- Pro


Moire Shader
GeometryBlack-and-white beats between gratings
- Pro


Quasicrystal Shader
GeometryA never-repeating lattice of rosettes
- Pro


Platonic Solids Shader
GeometryFine brass lines on navy
- Pro


Simplex Truchet Weave Shader
Patterns & tilingsCords woven over and under
- Pro


Perspex Web Lattice Shader
Tunnels & 3DA crafted surface for a premium page
- Pro


City Lights Shader
Water & landscapesA living city for a fintech hero