Kinetic Typography

Material Fill Text

Large type filled with procedurally generated materials — wood grain, brushed metal, paper noise, marble and holo foil — cross-fading on autoplay or hover with a sweeping highlight, and no texture images loaded.

Present a headline as if it were made of material. The text itself is transparent; underneath sit five material layers generated live from CSS gradients and inline SVG feTurbulence noise — wood grain, brushed metal, paper, marble and holo foil — revealed only through the glyphs with background-clip: text. On each switch the new material cross-fades in while its texture glides forward a little, then a diagonal highlight sweeps across, like light passing over a real surface. It can autoplay, switch on hover, or follow a controlled material; change the seed for a different texture.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add material-fill-text.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

2200
7
Active edited usageDefault props
<MaterialFillText/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add https://webberui.com/r/material-fill-text.en.json

Or, once registries are configured in components.json, install it as @webberui/material-fill-text.

Usage

import { MaterialFillText } from "@/components/ui/material-fill-text";

// Autoplay through all five materials
<MaterialFillText className="text-8xl font-black tracking-tight">
  質地
</MaterialFillText>

// Advance on hover, using only three materials
<MaterialFillText mode="hover" materials={["metal", "foil", "wood"]}>
  CRAFT
</MaterialFillText>

// Controlled: an external control decides the material
const [material, setMaterial] = React.useState<MaterialFillKind>("marble");

<MaterialFillText mode="manual" material={material} onMaterialChange={setMaterial}>
  石與木
</MaterialFillText>

Props

PropTypeDefaultDescription
childrenstring—Text to fill with material (plain string; every material layer stacks an identical copy)
materialsMaterialFillKind[]["wood", "metal", "paper", "marble", "foil"]Materials in the rotation, in order; the first one is also where it stays under reduced motion
materialMaterialFillKind—Current material in controlled mode; managed internally when omitted (uncontrolled)
defaultMaterialMaterialFillKindmaterials[0]Initial material in uncontrolled mode
onMaterialChange(material: MaterialFillKind) => void—Called when the material changes (fires for both autoplay and hover switching)
mode"auto" | "hover" | "manual""auto"auto rotates on a timer, hover advances on pointer enter, manual follows the controlled material only
intervalnumber2200Rotation interval in auto mode (ms); when omitted it reads --wb-material-fill-interval
sheenbooleantrueSweep a diagonal highlight band across the text once on each switch (600ms)
seednumber7Random seed for the procedural noise; change the number to get a different texture
as"h1" | "h2" | "p" | "span""h2"Root element tag
classNamestring—Passed to the root element; font size and weight come from here (default text-6xl font-black tracking-tight)

MaterialFillKind

FieldTypeDefaultDescription
"wood"string—Wood grain: layered brown stripes at slightly different angles plus stretched feTurbulence noise
"metal"string—Brushed metal: 1px greyscale hairlines and a diagonal highlight band
"paper"string—Paper: a paper-tone base with fine fractalNoise specks
"marble"string—Marble: turbulence noise turned into veins with feColorMatrix
"foil"string—Holo foil: a multicolour conic-gradient whose colours shift on hover

How it works

  • Structure: one transparent real-text layer handles layout, selection and screen readers; above it every material stacks its own copy of the text (absolute, aligned, aria-hidden). The current material's layer is at opacity: 1, the rest at 0.
  • The pacing is overridable with CSS variables: --wb-material-fill-duration (cross-fade, default 450ms) and --wb-material-fill-interval (rotation interval, default 2200ms; the interval prop wins).
  • Noise is assembled as a data:image/svg+xml string inside the component, with no external URLs; the string building is deterministic, so server and browser output match.
  • Every material has a light and a dark palette: light surfaces get the deeper versions (dark wood, dark steel, kraft paper, black marble), and dark mode (dark:) switches to brightened versions so the text keeps enough contrast.
  • Rotation pauses automatically while the text is scrolled out of view; in controlled auto mode the next switch is scheduled only after the parent writes material back.
  • Material type is a display effect meant for large headings; keep body copy and small text in a regular text colour.

Accessibility

  • The real text layer stays in the DOM, so it can be selected and copied and is read once by screen readers; every material layer and the sheen layer are aria-hidden, so nothing is announced twice
  • In forced-colors mode (Windows High Contrast) the material layers are hidden and the real text falls back to the system text colour, so the content never disappears
  • The switching is decorative and carries no information; if the material name matters, show it as text alongside, as the demo does
  • When the user has enabled "reduce motion", it stays on the first material with no rotation and no sheen, and mode="hover" does not switch either; controlled manual mode still shows the requested material but switches instantly without a cross-fade