Skip to content

Extruded Type Tilt

Display type with a solid extrusion that swings around as the pointer moves.

Freebrutalisteditorialneon
Category
Text Effects
Added
2026-09-20
Deps
none
Updated
2026-09-20
Preview
DEPTH

Props

10
2.2
Bright front face
No runtime dependencies
"use client";

import { useRef, useState } from "react";

export function ExtrudedTypeTilt({
  text,
  layers = 10,
  depth = 2.2,
  accentFace = true,
}: {
  text: string;
  layers?: number;
  depth?: number;
  /** Bright front face. Off, the face sits closer to the extrusion tone. */
  accentFace?: boolean;
}) {
  const boxRef = useRef<HTMLDivElement>(null);
  // Extrusion direction. Defaults to down-right — the conventional light
  // position — so the solid reads correctly before any pointer input.
  const [dir, setDir] = useState({ x: 0.7, y: 0.7 });

  function handleMove(e: React.MouseEvent<HTMLDivElement>) {
    const rect = boxRef.current?.getBoundingClientRect();
    if (!rect) return;
    const nx = (e.clientX - rect.left) / rect.width - 0.5;
    const ny = (e.clientY - rect.top) / rect.height - 0.5;
    // Inverted, so the extrusion trails AWAY from the cursor like a shadow
    // cast by it rather than leaning toward it.
    setDir({ x: -nx * 2, y: -ny * 2 });
  }

  return (
    <div
      ref={boxRef}
      onMouseMove={handleMove}
      onMouseLeave={() => setDir({ x: 0.7, y: 0.7 })}
      className="flex cursor-default select-none items-center justify-center"
    >
      <span className="relative block text-6xl font-black leading-none tracking-tight">
        <span className="sr-only">{text}</span>

        <span aria-hidden>
          {/*
           * The depth is N stacked copies, each offset one step further along
           * the light direction and darkened. Because only the OFFSET
           * direction changes, the solid appears to rotate without any real
           * 3D — no perspective, no preserve-3d, and so no GPU rasterisation
           * blurring the glyph edges at fractional transforms.
           * Furthest layer paints first so nearer ones cover it.
           */}
          {Array.from({ length: layers }).map((_, i) => {
            const step = layers - i;
            return (
              <span
                key={i}
                className="absolute inset-0 block transition-transform duration-200 ease-out"
                style={{
                  transform:
                    "translate(" +
                    dir.x * depth * step +
                    "px, " +
                    dir.y * depth * step +
                    "px)",
                  color: "rgba(61,123,255," + (0.06 + (i / layers) * 0.3) + ")",
                }}
              >
                {text}
              </span>
            );
          })}

          {/* Front face, drawn last and unoffset. */}
          <span
            className="relative block"
            style={{ color: accentFace ? "#e8eaee" : "#9aa3b2" }}
          >
            {text}
          </span>
        </span>
      </span>
    </div>
  );
}
Notes

About this text effect

The obvious way to build extruded type that turns with the cursor is real 3D — perspective on the container, preserve-3d, a rotation on two axes. It also looks worse, because the moment text is rotated in 3D the browser rasterises it to a texture and the glyph edges go soft, which is exactly the wrong outcome for display type whose whole appeal is its crispness. This fakes the rotation instead: the depth is a stack of flat copies, each offset one step further along a direction vector and darkened, and only that direction changes with the pointer. The solid appears to swing while every layer stays a normally-rendered, sub-pixel-antialiased glyph. Paint order matters — the furthest layer goes down first so nearer ones cover it, otherwise the side wall reads inside out. Defaulting the direction to down-right gives it a conventional light position at rest and on mouse leave, so it never looks unlit. The trade-off is duplication: a dozen layers is a dozen copies of the word, so it belongs on one short headline and needs a single hidden copy carrying the real string.

Curator’s note

Built for viberdy 2.0. Faking the rotation with offset direction instead of real 3D is what keeps the glyph edges razor sharp.

Related

Pairs well with

Matched on shared tags and category — the entries most likely to be used alongside this one.

Trending

Scramble Text

text effects

Text that decodes from random characters into its real word on hover.

texthoverdecode

Glitch Text Hover

text effects

An RGB-split, jittery glitch effect on text when you hover it.

textglitchhover
Featured
New

Slice Shear Hover

text effects

Display type cut into horizontal bands that fan apart under the pointer.

hovertextclip-path