Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Shaders

Pro

Pixel Sort Shader for React

A pixel-sorted dusk drawn on the GPU: an indigo sky, a low orange sun and dark hills, melted into thin streaks that run dark to bright and slowly grow and retreat. For React with Base UI, when an album release, a photography portfolio or an editorial hero wants a calm, glitch-art texture.

Framework

Pixel Sort Shader draws a dusk landscape, an indigo sky with drifting cloud bands, a low orange sun and two ridges of dark hills, and melts it the way pixel-sorting artists do. In each thin column, every run of pixels brighter than a threshold is sorted by brightness, so it becomes a streak that runs dark at the top to bright at the bottom, piling the sun's glow onto the hills. The threshold slowly breathes, so the streaks reach up into the sky and draw back again. A WGSL fragment shader on WebGPU computes every pixel each frame, sorting nothing: it finds each run's ends and fills it with the ramp a sort would make.

Use it behind an album release, a photography portfolio, a music or editorial hero, or as a banner strip, wherever glitch art should feel calm rather than broken. For datamosh-style tearing and flashes of static, use Glitch Shader; for the same kind of dusk drawn in CRT phosphor, CRT Phosphor Shader. It runs only while it's on screen, holds a still frame with reduced motion, and shows a striped gradient of the same dusk 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 { PixelSortShader } from "@/components/ui/pixel-sort-shader";
<PixelSortShader className="flex min-h-[32rem] items-start justify-center pt-20">
  <h1 className="text-5xl font-semibold text-white">Night Drive</h1>
</PixelSortShader>

Pixel Sort Shader is a container: your content sits on top of the scene, 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 scene is dark in both color schemes, so use white or light text, in the indigo sky at the top or on the hills at the bottom; keep it off the bright band in between.

Demos

direction="right" sorts rows instead of columns, so the glow smears sideways into long horizontal streaks, which suits a short, wide strip. intensity={1.3} lowers the threshold, so more of the sky joins the sort.

Preview width

In a card

columnWidth={4} makes each sorted column 4 CSS pixels wide, chunkier streaks that still read on a small surface.

Preview width

Performance

About 2 ms of GPU time a frame at 1280×720 on an M2 Max laptop; slower GPUs take several times longer. Pixels outside a streak just draw the scene; pixels inside one sample their column every 4 CSS pixels or more, at most 48 times, to find the streak's ends. It keeps full quality: the columns are a couple of pixels wide, and half resolution would blur them. To spend less, lower streak, which shortens the runs each pixel walks. 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 pinstriped CSS gradient of the same dusk over dark hills, in your colors, 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 scene 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 sorted dusk and stops. Nothing flashes: the threshold rises and falls over about 18 seconds, so the streaks grow and shrink a few pixels at a time. White text reads above 8:1 in the top 40% of the box and above 19:1 on the hills; over the glow it drops to nothing.

FAQ

How does it sort pixels in a shader?

It doesn't sort anything. A run of pixels sorted by brightness is always a ramp from its darkest to its brightest, so each pixel only has to find its run: it walks up and down its column, sampling the scene every 4 CSS pixels or more (at most 48 samples), until the brightness drops below the threshold, and keeps the tone at the top of the run and the brightest tone in it. Its color is the ramp between them, at its place in the run. Columns are cut into lengths of at most streak, staggered so the cuts don't line up, and each column's threshold is nudged a little, so the streaks end at different heights.

How do I change how much gets sorted?

intensity sets the threshold: at 0 nothing is sorted and you see the plain dusk, at 1 the glow and the lower sky melt, and at 2 almost the whole sky does. streak caps how long a streak can run, as a share of the box, and columnWidth sets how wide each sorted column is, from 1 to 6 CSS pixels.

Can the streaks run another way?

Yes: direction takes "down" (the default: dark at the top, bright at the bottom), "up" (the reverse, so the glow climbs into the sky) or "right" (rows instead of columns, dark at the left, bright at the right).

Can I change the colors?

Yes: skyColor is the night sky, and the hills are a darker shade of it; glowColor is the sun's glow and the warm band above the hills; highlightColor is the sun and the brightest ends of the streaks. They take any CSS color, var(--primary) included, and follow a theme switch. Keep them going from dark to light, sky to glow to highlight, so the sorted ramps stay smooth.

Will it slow my page down?

It costs about 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. On a slow GPU, lower streak or intensity before quality: shorter or fewer streaks mean fewer samples, while half resolution blurs the columns.

What happens in browsers without WebGPU?

The box keeps its CSS still, a pinstriped gradient from indigo through orange to cream, over dark hills, so the page still looks finished. Nothing errors: the root's data-state reads unsupported.

API Reference

PixelSortShader

data-slot="pixel-sort-shader"

PropType
columnWidth?number

How wide each sorted column is, in CSS pixels, from 1 to 6. @default 2.

Default
2
direction?"down" | "right" | "up"

Which way the streaks run, dark to bright: down and up sort columns, right sorts rows. @default "down".

Type
"down" | "right" | "up"
Default
"down"
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.

glowColor?string

The sun's glow and the warm band above the hills. Any CSS colour. @default "#ff8a5b".

Default
"#ff8a5b"
highlightColor?string

The sun and the brightest ends of the streaks. Any CSS colour. @default "#ffe3b3".

Default
"#ffe3b3"
intensity?number

How much of the scene gets sorted, from 0 (none) to 2 (almost all the sky). @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.

skyColor?string

The night sky at the top; the hills are a darker shade of it. Any CSS colour. @default "#1a1446".

Default
"#1a1446"
speed?number

Animation speed: 1 is normal, 0 holds the frame. @default 1.

streak?number

The longest a streak can run, as a share of the box, from 0.1 to 1. @default 0.6.

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