shadcn.io is not affiliated with official shadcn/ui
Incense Shader draws a thread of smoke rising from a stick below the box. For the first third it's perfectly smooth and straight; then waves start to travel up it, grow, fold into curls and finally open into a soft veil that fades near the top. Two fainter, out-of-focus threads drift behind it. A WGSL fragment shader on WebGPU computes every pixel each frame: each thread is a center line of three travelling sine waves, bent by simplex noise once it turns turbulent, and drawn as a soft line that widens and thins as it climbs.
Use it beside the headline of a spa, tea, fragrance or wellness page, or anywhere one quiet motion suits better than a full-screen effect. The thread rises at 70% of the width by default, leaving the left side calm for copy. For broad plumes that fill the bottom of the box, use Smoke Shader; for paper marbling on a light ground, Marbling Shader; for CSS puffs that follow your theme, the Smoke background. It runs only while it's on screen, holds a still frame with reduced motion, and shows a soft gradient of the thread in browsers without WebGPU.
Installation
Usage
import { IncenseShader } from "@/components/ui/incense-shader";<IncenseShader className="flex min-h-[32rem] items-center px-12">
<h1 className="max-w-sm text-5xl font-semibold text-white">Slow down</h1>
</IncenseShader>Incense Shader is a container: your content sits on top of the smoke, 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 ground is charcoal in both color schemes, so use white or light text, on the side away from the thread: origin moves the thread, so set it to 0.3 for copy on the right.
Demos
On a light page
backgroundColor and smokeColor recolor the ground and the smoke: here slate smoke on warm paper. The smoke blends between the two, so any pair works, light on dark or dark on light.
In a card
origin={0.5} raises the thread up the middle of the card, and threads={1} drops the two behind it, so a narrow box shows one clean line.
Performance
About 0.5 ms of GPU time a frame at 1280×720 on an M2 Max laptop; slower GPUs take several times longer. Pixels too far from a thread for it to reach skip it, so the empty side of the box costs almost nothing. It keeps full quality: the thread's base is only a couple of CSS pixels wide, and half resolution would blur it. 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 gradient of a thread and its veil, in your smokeColor and backgroundColor, 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 smoke is decoration: it's hidden from assistive tech and lets every click through to your content. With reduced motion it draws one still frame, the thread rising into its first curls, and stops. Nothing flashes: the waves travel up the thread over seconds. White text reads above 14:1 on the half of the box away from the thread at the default origin; keep text off the thread itself, where the contrast drops.
FAQ
How is the smoke drawn?
Each thread is a center line made of three sine waves that travel upward. Below a transition height their amplitude is zero, so the thread is straight; above it the amplitude grows with height, and simplex noise warps the space around the line, folding the waves into curls. The line is drawn as a soft Gaussian of the distance to it, corrected for its slope, widening and fading toward the top, and a little brighter on the side facing the light.
What does turbulence do?
It sets how early the smoke turns turbulent. At 0 the thread stays straight for half the box and curls only near the top, very calm; at the default 0.5 it breaks at about a third; at 1 it starts to wave almost from the bottom.
How do I move the thread?
origin places the main thread across the box, from 0 (the left edge) to 1 (the right edge). The two fainter threads stay beside it. threads sets how many rise, from 1 to 3, and intensity how opaque the smoke is.
Can I change the colors?
Yes: smokeColor sets the smoke and backgroundColor the ground. They take any CSS color, var(--primary) included, and follow a theme switch. For a light page, pair a light ground with a dark smoke, as the demo above does.
API Reference
IncenseShader
data-slot="incense-shader"
backgroundColor?string
The ground behind the smoke. Any CSS colour. @default "#121418".
- Default
"#121418"
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
How opaque the smoke is, from 0 (none) to 2 (dense). @default 1.
- Default
1
interactive?boolean
Whether u.pointer follows the pointer. @default false.
origin?number
Where the main thread rises, from 0 (the left edge) to 1 (the right edge). @default 0.7.
- Default
0.7
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.
smokeColor?string
The smoke. Any CSS colour. @default "#c9d3e0".
- Default
"#c9d3e0"
speed?number
Animation speed: 1 is normal, 0 holds the frame. @default 1.
threads?number
How many threads rise: the main one, then two fainter ones behind it, from 1 to 3. @default 3.
- Default
3
time?number
A fixed moment, in seconds: draws it and doesn't animate.
turbulence?number
How early the smoke breaks into curls, from 0 (high up, calm) to 1 (low, restless). @default 0.5.
- Default
0.5
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


Smoke Shader
Ink, smoke & fireBroad plumes that fill the bottom of the box
- Pro


Ink Vortex Shader
Ink, smoke & fireInk rings sinking slowly, on a light ground
- Pro


Marbling Shader
Ink, smoke & firePaper marbling on a light ground
- Pro


Lattice Light Shader
Light & glassWarm, quiet light for a wellness page


Valley Mist Shader
Water & landscapesA calm ink-wash landscape, free


Streamlines Shader
Flow & gradientsHairlines that part round shapes, free