Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Shaders

Pro

Incense Shader for React

A single thread of smoke drawn on the GPU: it rises smooth and straight, starts to wave, curls and opens into a soft veil, pale grey-blue on charcoal. For React with Base UI, when a spa, tea or fragrance page wants one quiet, slow motion beside its headline.

Framework

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.

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

Preview width

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.

Preview width

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.

Will it slow my page down?

It costs about 0.5 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. Keep quality at 1: the thin base of the thread blurs at half resolution, and there's little to save.

What happens in browsers without WebGPU?

The box keeps its CSS gradient, a faint column of smoke and a soft glow above it in your colors, so the page still looks finished. Nothing errors: the root's data-state reads unsupported.

API Reference

IncenseShader

data-slot="incense-shader"

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

All 101 shaders