Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Shaders

Pro

Smoke Shader for React

Soft grey plumes that rise from the bottom of a near-black navy backdrop, churning and thinning as they climb. For React with Base UI, when a dark hero, an event page or a moody section wants slow, textured smoke behind its copy.

Framework

Smoke Shader draws plumes of smoke on the GPU. Two soft grey columns rise from the bottom edge, with a third at the right, each widening as it climbs, swaying side to side and gone by about the middle of the box. Layers of fractal noise churn inside them, a faint haze drifts in the upper half, and a warm tint and a soft diagonal light catch the smoke near its base. Every pixel is computed each frame by a WGSL fragment shader on WebGPU, so the texture keeps rolling upward in a way CSS can't.

Use it behind a dark hero, an event or music page, or a section that should feel smoky and slow. The upper half stays dark and calm, so copy sits there. For the same idea in plain CSS, with puffs that follow your theme, use the Smoke background; for one thin thread of smoke that curls, Incense Shader. It runs only while it's on screen, holds a still frame with reduced motion, and shows a gradient of the same plumes 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 { SmokeShader } from "@/components/ui/smoke-shader";
<SmokeShader className="flex min-h-[32rem] items-start justify-center pt-24">
  <h1 className="text-5xl font-semibold text-white">Ship faster</h1>
</SmokeShader>

Smoke 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 backdrop is near-black in both color schemes, so use white or light text, and keep it in the upper half, where the smoke has thinned.

Demos

On a light page

backgroundColor sets the backdrop and smokeColor the smoke; denser smoke shades darker from it. Here a dark grey on zinc gives charcoal smoke on a light page, and density={0.8} thins it so the plumes stay soft instead of sooty.

Preview width

In a card

The plumes scale with their box. density={1.3} thickens them and height={1.2} lets them climb and spread further, so they fill a small, tall surface.

Preview width

Performance

About 1.5 ms of GPU time a frame at 1280×720 on an M2 Max laptop; slower GPUs take several times longer. Each pixel skips the plumes it's outside of, so the dark upper corners cost little. The smoke is soft, so quality={0.5} renders a quarter of the pixels and looks nearly the same, on a slow GPU or a large screen. 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 the same plumes, 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 of the plumes and stops. Nothing flashes: the smoke drifts and rolls over seconds. White text reads above 13:1 everywhere in the upper half, and stays above 4.5:1 even over the brightest smoke at the bottom, so large text is safe anywhere and body text belongs up top.

FAQ

Is it real volumetric smoke?

No: it's 2D. Each plume is a cone that widens with height and fades toward a top that wobbles over time, filled with layers of value noise that scroll upward, plus a pseudo-3D noise made of three 2D slices. There's no fluid simulation or ray marching, which is why it's cheap, and the soft result reads as smoke.

How do I use it on a light page?

Set a light backgroundColor and a dark smokeColor, as in the demo above: backgroundColor="#f4f4f5" with smokeColor="#52525b" gives charcoal smoke on zinc. Lower density a little so the base of the plumes stays grey rather than black.

What do height, dissipation and turbulence do?

height sets how high the plumes reach and how wide they spread as they climb, from 0.3 (short stubs) to 1.5 (tall, broad columns). dissipation sets how fast they thin with height: 0 keeps them thick to the top of the cone, and 2 fades them out a third of the way up. turbulence sets how much the texture inside churns, from 0 (smooth bands) to 2 (rough and broken).

Can I change the smoke color?

Yes: smokeColor sets the smoke where it's thin, and the denser parts shade darker from it, with a warmer tint near the base. backgroundColor sets the backdrop as it shows at the bottom edge; it darkens a little toward the top. Both take any CSS color, var(--primary) included, and follow a theme switch.

Will it slow my page down?

It costs about 1.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. On a slow GPU or a large screen, quality={0.5} renders at half resolution, a quarter of the pixels, and the soft smoke looks the same.

What happens in browsers without WebGPU?

The box keeps its CSS gradient, soft plumes rising from the bottom in your colors, so the page still looks finished. Nothing errors: the root's data-state reads unsupported.

API Reference

SmokeShader

data-slot="smoke-shader"

PropType
backgroundColor?string

The backdrop, at the bottom edge; the grade darkens it a little toward the top. Any CSS colour. @default "#050b14".

Default
"#050b14"
density?number

How thick the smoke is, from 0 (none) to 2 (dense). @default 1.

Default
1
dissipation?number

How fast the smoke thins as it rises, from 0 (it doesn't) to 2 (it's gone by mid-height). @default 1.

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

height?number

How high the plumes reach and how wide they spread, from 0.3 to 1.5. @default 1.

Default
1
interactive?boolean

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

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's colour where it's thin; denser smoke shades darker from it. Any CSS colour. @default "#e6e6e6".

Default
"#e6e6e6"
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.

turbulence?number

How much the texture inside the plumes churns, from 0 (smooth) to 2 (rough). @default 1.

Default
1
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