Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Shaders

Pro

Advanced Tunnel Shader for React

A flight down a winding tunnel drawn on the GPU, lined with rings of glossy rounded tiles whose colors shift ring by ring toward a warm glow at the far end. For React with Base UI, when a game, an arcade launch or a playful hero should pull the eye forward.

Framework

Advanced Tunnel Shader flies a camera down a winding tunnel on the GPU. A WGSL fragment shader on WebGPU ray marches a tube that snakes along two slow sine curves, then tiles its wall with rings of rounded, bricked tiles: each tile's bevel bends the light, a lamp ahead of the camera puts glossy highlights on them, and their colors slide through the spectrum ring by ring. The far end burns with a warm white-gold glow that the tiles reflect, and the camera banks gently into the turns.

Use it behind a game or arcade launch, a playful hero or a full-screen intro that should feel like speed. For a darker neon tunnel with scanlines, use Digital Tunnel Shader; for a metal tube with glowing cells, Oldschool Tube Shader; for a grid tunnel in plain CSS, with no GPU, the Warp background. It runs only while it's on screen, holds a still frame with reduced motion, and shows a CSS gradient of the same rings 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 {
  AdvancedTunnelShader,
} from "@/components/ui/advanced-tunnel-shader";
<AdvancedTunnelShader className="flex min-h-[32rem] items-center justify-center">
  <div className="rounded-xl bg-black/60 px-8 py-6 backdrop-blur-sm">
    <h1 className="text-5xl font-semibold text-white">Press start</h1>
  </div>
</AdvancedTunnelShader>

Advanced Tunnel Shader is a container: your content sits on top of the tunnel, 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 tiles are bright and busy in both color schemes, so set text on a dark panel, as above, rather than straight on the tunnel.

Demos

Chrome

variety={0} turns every tile grey, so the tunnel reads as polished metal, glowColor="#7dd3fc" makes the far light an icy blue that the tiles reflect, and bevel={1.6} rounds the tiles deeper.

Preview width

In a card

The tunnel scales with its box. radius={1.6} widens it, so the tiles near the camera are larger and calmer in a small space, and speed={0.6} slows the flight.

Preview width

Performance

About 0.9 ms of GPU time a frame at 1280×720 on an M2 Max laptop, so it renders at full resolution; slower GPUs take several times longer. Rays toward the far end take the most steps, up to 110, and the tile shading runs once per pixel where the ray lands. The props barely change the cost. quality={0.75} renders about half the pixels and keeps the near tiles crisp; at quality={0.5} the grout lines soften. 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 rings in the same colors around a warm core, 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 tunnel is decoration: it's hidden from assistive tech and lets every click through to your content. With reduced motion it draws one still frame, rings of violet, blue and magenta tiles around the glow, and stops. Nothing flashes: about three rings slide past each second at the default speed, but only a thin line of grout crosses any spot at a time, and the colors change gradually. Text needs a panel: across the renders we measured, bare white text reaches only about 4:1 on a typical pixel. White text on a bg-black/50 panel stayed above 4.5:1 on every pixel, and on bg-black/60 above 6.4:1.

FAQ

How are the tiles drawn without textures?

Each pixel works out where it hits the tunnel wall, as an angle around the tube and a depth along it. That gives it a cell in a grid of rings, bricked so every other ring is offset by half a tile, and a rounded-rectangle distance gives the tile's height inside the cell. The shader tilts the surface by the slope of that height, so the bevels catch the light.

What makes the glow at the end?

Rays that travel far fade into glowColor, and a soft halo sits on the direction the tunnel heads in. Nearby tiles reflect the same light at grazing angles, so the warm tone runs along the walls. glow sets its brightness, from 0 (a dark far end) to 2.

Can I make it one color?

variety={0} makes every tile grey, a chrome tunnel lit by glowColor, so a brand color there tints the whole scene. variety={1.5} goes the other way and gives every tile its own hue. In between, the colors shift a little from ring to ring and drift slowly over time.

How do I make the tunnel wider or narrower?

radius sets its width, from 0.8 (a tight tube with small tiles close to the camera) to 2 (a wide hall). There are always 18 tiles around a ring, so a wider tunnel has wider tiles.

How do I make it faster or slower?

speed scales time: 2 flies twice as fast, 0.5 half as fast, and 0 holds the frame. Past speed={1.5}, more than four rings pass each second, which feels hectic behind text. paused holds the frame, and time draws one fixed moment, in seconds.

Will it slow my page down?

It costs about 0.9 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.75} renders about half the pixels and keeps the near tiles sharp.

Credits

Inspired by a tile tunnel shader (opens in a new tab) by OldEclipse on Shadertoy. This is a new shader written from a description of the look, not a port: the winding path, the tile pattern and bevels, the lighting, the palette and the code are ours. The rounded box distance and the cosine palette follow Inigo Quilez's articles (MIT), and the hash is lowbias32 by Chris Wellons, in the public domain; each keeps its credit line in the WGSL.

API Reference

AdvancedTunnelShader

data-slot="advanced-tunnel-shader"

PropType
bevel?number

How rounded the tiles stand out from the grout, from 0 (flat) to 2 (deep pillows). @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.

glow?number

How bright the light at the far end burns, from 0 (none) to 2. @default 1.

Default
1
glowColor?string

The light at the far end, and the shine it puts on the tiles. Any CSS colour. @default "#ffc771".

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

radius?number

How wide the tunnel is, from 0.8 (narrow) to 2 (wide). @default 1.2.

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

variety?number

How colourful the tiles are, from 0 (grey) to 1.5 (every tile its own hue). @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