Words spiralling into a tunnel of type: rings of crisp, repeating text turn against each other and dissolve into a light (or a hole) at the vanishing point, with fog, motion blur, aberration, a pointer that bends the tunnel, scroll-driven speed and a warp on click.

  • shader
  • webgl
  • text
  • typography
  • tunnel
  • vortex
  • hero
  • scroll
  • pointer
Preset

Installation

Pro — coming soon

The source and installation instructions for this item ship with Kagadmodyaa Pro.

Usage

Use it like any other component. Choose a theme with preset; any prop you pass as well overrides the preset.

import { useRef } from 'react'
import { TypeVortex, type TypeVortexHandle } from '@/components/effects/type-vortex/type-vortex'

export function Hero() {
  const vortex = useRef<TypeVortexHandle>(null)
  return (
    <section className="relative h-[640px] overflow-hidden">
      {/* fills its parent; children are drawn over it, so a crisp headline sits on the type */}
      <TypeVortex ref={vortex} preset="Studio" words={['Design', 'Motion', 'Brand', 'Story']} direction="in" centerX={0.7} className="absolute inset-0">
        <div className="absolute inset-y-0 left-0 flex max-w-md flex-col justify-center p-12">
          <h1>Words with a pull.</h1>
          <button className="pointer-events-auto" onClick={() => vortex.current?.surge()}>See the work</button>
        </div>
      </TypeVortex>
    </section>
  )
}

// Scroll the page to fly through it: down dives in, up climbs out.
export function Scrolly() {
  return <div className="sticky top-0 h-screen"><TypeVortex preset="Prism" scrollDriven pointer={0.4} /></div>
}

// Presets: Studio, Infrared, Mint, Paper (ink on cream, a dark centre, for light pages), Prism. Any prop overrides the preset.
// `words` takes an array (up to six) or one string split on "·", "|" or ",". font="site" uses your page's own font.
// The stage above is <TypeVortexShowcase />, also exported: the vortex with a headline and a button over it.
<TypeVortex preset="Infrared" words={"Design · Motion · Brand · Story · Craft · Type"} />

Presets

  • Studio
  • Infrared
  • Mint
  • Paper
  • Prism

Props

NameTypeDefaultDescription
preset'Studio' | 'Infrared' | 'Mint' | 'Paper' | 'Prism'none (the defaults below)A named theme. Any prop you pass as well overrides the preset’s value.
words'Design · Motion · Brand · Story · Craft · Type' | 'Create · Imagine · Build · Ship · Repeat · Again' | 'Signal · Pulse · Echo · Orbit · Static · Drift' | 'Breathe · Bloom · Settle · Drift · Return · Rest' | 'Studio · Atelier · Workshop · Foundry · Press · Lab''Design · Motion · Brand · Story · Craft · Type'The rows of type, one word per ring, cycling outward. A prop can also be an array of up to six strings.
font'site' | 'sans' | 'serif' | 'mono' | 'display''site''site' follows the page's own font; the others are system stacks, so nothing is downloaded.
weightnumber800Font weight. A variable or multi-weight font follows it. Range 300 to 900.
uppercasebooleantrueSet the words in capitals.
outlinebooleantrueEvery second ring is drawn as an outline in the accent colour.
trackingnumber0.02Letter spacing in em. Range -0.04 to 0.3.
speednumber1Animation speed. 0 freezes the motion. Range 0 to 2.5.
direction'in' | 'out''in''in' pulls the words toward the vanishing point, 'out' pushes them off the screen.
twistnumber0.2How far the rings corkscrew around the centre; the sign sets the handedness. Range -1 to 1.
ringsnumber1Higher means more, thinner rings with smaller type. Range 0.6 to 1.8.
fognumber0.95How quickly the rings dissolve into the centre. Range 0 to 1.5.
glownumber1Size and brightness of the light (or hole) at the vanishing point. Range 0 to 1.5.
aberrationnumber0.45Colour fringing that grows toward the edges. Range 0 to 1.
blurnumber0.5Speed-linked blur: sharp at rest, streaked when the vortex surges. Range 0 to 1.
colorstring'#f4f4f6'Colour of the type. A hex colour.
accentstring'#8e8e9a'Outline rings, the hairlines between rings and the depth tint. A hex colour.
backgroundstring'#050506'The space between the type. A light colour makes a vortex for light sites. A hex colour.
centerColorstring'#ffffff'What the vortex dissolves into: white-hot for a light, near-black for a hole. A hex colour.
spectrumnumber0Tints each ring a different hue. Range 0 to 1.
pointernumber0.7How far the pointer shifts the vanishing point and twists the vortex. 0 ignores it. Range 0 to 1.
scrollDrivenbooleanfalseScrolling the page drives the speed and direction: down dives in, up climbs out.
surgeOnClickbooleantrueA click or tap surges speed, blur and light. Call surge() on a ref to do it from code.
centerXnumber0.5Horizontal position of the vanishing point. On the stage it shifts the point from its designed spot. Range 0 to 1.
centerYnumber0.5Vertical position of the vanishing point. On the stage it shifts the point from its designed spot. Range 0 to 1.
zoomnumber1Pulls the camera in: more of the box is crisp type and the light at the centre shrinks. Range 0.6 to 2.
grainnumber0.3Amount of film grain (a fine dither is always on, so gradients never band). Range 0 to 1.
classNamestring—Extra class on the root element. The effect fills its parent: give the parent a height.
styleCSSProperties—Inline style on the root element.

Behaviour

  • Every word is a ring of real type. It is drawn to an offscreen canvas at high resolution (the page font or a system stack), uploaded once as a texture and wrapped around the vanishing point in log-polar coordinates, so the letters stay undistorted all the way down the tunnel. Rings turn in alternating directions at their own speeds and stream toward the centre (or away from it with direction="out").
  • The type stays sharp because the texture carries four hand-built mip levels per ring and the shader picks the level from the exact on-screen footprint. Toward the centre the rings dissolve into the centre colour with depth fog, so the vanishing point is a clean light (or hole), never noise. A fine dither keeps the gradients free of banding.
  • The pointer leans the vanishing point toward the cursor (the rim stays put, so the tunnel bends) and twists the vortex. On touch, dragging does the same. pointer={0} ignores it.
  • A click or tap warps: speed, blur and light surge, then settle. Call surge() on a ref to do it from code (surge(1.5) for a stronger one). surgeOnClick turns the click off.
  • scrollDriven ties the motion to the page scroll: scroll down and the vortex dives in, scroll up and it climbs out, faster as you scroll faster. It idles slowly when you stop. The direction prop flips which way is "down".
  • Motion blur follows the speed: sharp at rest, a radial streak when the vortex surges. Chromatic aberration grows toward the edges.
  • The words are in the page as visually hidden text (or your label), the canvas is hidden from assistive technology, and any children are ordinary DOM drawn on top.
  • It pauses off screen and in a background tab, lowers its resolution automatically if frames run slow, and draws one composed still frame under prefers-reduced-motion. Without WebGL, and before the first frame, you see a poster of concentric SVG text rings (they turn slowly when WebGL is missing).

See all effects