Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Shaders

Pro

Minimal Jigsaw Shader for React

A flat jigsaw puzzle drawn on the GPU: square pieces with round knobs that interlock, in a palette of soft colors, drifting slowly. For React with Base UI, when a playful hero, an onboarding step or a team page wants a pattern about fitting together.

Framework

Minimal Jigsaw Shader draws an assembled jigsaw puzzle that drifts slowly across the box. Every piece is a square from a grid; each of its four edges gets one round knob on a narrow neck, sticking out one way or the other at random, and the neighbour on that side is cut to take it. Each piece is a flat color picked from a palette, and the cuts are thin, crisp lines. A WGSL fragment shader on WebGPU computes every pixel from the grid, so the outlines stay sharp at any size.

Use it behind a playful hero, an onboarding or "you're all set" step, or a team or partners page, where fitting together is the point. For a quieter grid with no knobs, in plain CSS, use the Grid Pattern background; for a pattern of curves that join up from tile to tile, Truchet Shader. It runs only while it's on screen, holds a still frame with reduced motion, and shows a CSS grid of 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 {
  MinimalJigsawShader,
} from "@/components/ui/minimal-jigsaw-shader";
<MinimalJigsawShader className="flex min-h-[32rem] items-center justify-center">
  <div className="rounded-xl bg-white/80 px-8 py-6">
    <h1 className="text-5xl font-semibold text-stone-950">Ship faster</h1>
  </div>
</MinimalJigsawShader>

Minimal Jigsaw Shader is a container: your content sits on top of the puzzle, 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 default pastels are light in both color schemes, so use dark text: every pastel gives black text above 14:1. The cut lines run through the letters, though, so a large headline reads bare, while smaller copy wants a panel: bg-white/80 keeps black text above 13:1 everywhere.

Demos

Earth

palette="earth" switches to terracotta, sage, sand, pine and rose. outlineColor="#fbf7f0" draws the cuts in cream, like gaps between pieces on a table, and knobSize={1.2}, the largest, gives every piece bigger knobs.

Preview width

In a card

pieceColors replaces the palette with your own colors, here four shades of indigo: pass one to five, and they repeat to fill five. pieces={5} fits five pieces to the height of the card, so they stay big enough to read as puzzle pieces. It keeps full quality: the cut lines are thin and blur at half resolution, and a card costs the GPU next to nothing.

Preview width

Performance

About 0.2 ms of GPU time a frame at 1280×720 on an M2 Max laptop; slower GPUs take several times longer. Each pixel checks its own grid square and the four knobs on its edges, with a few integer hashes and circle distances. Keep quality at 1: the lines are a pixel or two wide. 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 grid of squares in the first four piece colors, with the same outlines, 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 puzzle is decoration: it's hidden from assistive tech and lets every click through to your content. With reduced motion it draws one still frame and stops. Nothing flashes: the colors never change, and the puzzle drifts about a piece every six seconds. Every pastel gives black text above 14:1, but the cut lines cross your letters, so put body text on a bg-white/80 panel, which keeps black text above 13:1.

FAQ

How are the pieces drawn?

From one square grid. Each edge between two squares gets a round knob, a circle whose center sits just past the edge, so the edge cuts it into a narrow neck; a hash of the edge decides which way it points. A pixel belongs to its own square unless it's inside a neighbour's knob, and the outline is drawn where that changes.

Do the pieces really interlock?

Yes. Every knob is cut out of the piece it pokes into, so each piece's tabs fit its neighbours' blanks exactly, and no two knobs overlap, even at the largest knobSize.

How do I change the size of the pieces?

pieces sets how many fit the height of the box, from 4 to 16. They scale with the box, so a card shows the same number as a hero: lower it for a small box. knobSize sets the knobs, from 0.6 to 1.2.

Which palettes are there, and can I use my own colors?

palette picks one of four: pastel (the default), vivid, earth and mono. pieceColors replaces it with your own, one to five CSS colors, var(--primary) included, and they follow a theme switch. outlineColor sets the cut lines: dark on light pieces, or light for a cut-paper look on dark ones.

Can it stay still?

Yes: speed={0} or paused holds it where it is, and time draws one fixed moment, in seconds. speed also slows or quickens the drift: 0.5 is half as fast.

Will it slow my page down?

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. It's light enough to keep quality at 1, which keeps the lines crisp.

Credits

Inspired by Minimal Jigsaw (opens in a new tab) by Shane on Shadertoy: a jigsaw pattern from a grid and a round knob per edge. This shader is our own code and design, written from scratch: square pieces with necked knobs, rounded where they meet the edge, set palettes and your own colors, and a CSS-pixel outline.

API Reference

MinimalJigsawShader

data-slot="minimal-jigsaw-shader"

PropType
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.

interactive?boolean

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

knobSize?number

How big the knobs are, from 0.6 to 1.2. @default 1.

Default
1
outlineColor?string

The cut lines between pieces. Any CSS color. @default "#1c1917".

Default
"#1c1917"
palette?keyof typeof PALETTES

The set of colors the pieces pick from. @default "pastel".

Default
"pastel"
paused?boolean

Holds the current frame. @default false.

pieceColors?readonly string[]

Your own piece colors, one to five CSS colors: they replace the palette.

pieces?number

How many pieces fit the height of the box, from 4 to 16. @default 7.

Default
7
quality?number

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

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