Variable Axis Wave
A free variable font animation for React with configurable weight, wavelength and lift.
Crème brûlée, torched to order 👨🏽🍳
Customise
Code
<TextVariableAxisWave>
Crème brûlée, torched to order 👨🏽🍳
</TextVariableAxisWave>Variable Axis Wave is a free variable font animation React component for Next.js and Tailwind. This animated text effect drives one Motion time value to send a weight crest through grapheme-safe landing-page headlines, with configurable range, wavelength, speed, lift and direction. The loop pauses offscreen and in hidden tabs, the in-view wave runs once, and the hover wave reverses from wherever it is when released.
The complete string and every visual grapheme render in the server HTML. Assistive technology reads the original copy once, from a visually hidden copy inside the semantic wrapper, while emoji and combining marks stay intact. Reduced-motion visitors and strings over 40 graphemes receive a complete static midpoint state with no animation. Its lines break where the wave at its widest would break them, so the text wraps the same at every moment of the wave, keeps its natural spacing, and nothing around it moves, whether the wrapper fills its container or is sized to its text in a centred flex or grid. Render the wave as the block itself, for example as="h1", or give it className="block": an inline span's lines belong to its paragraph, so it keeps the natural layout.
Install
Add the @te registry to your components.json once:
{
"registries": {
"@te": "https://templateempire.io/r/{name}.json"
}
}Then install the component:
npx shadcn@latest add @te/text-variable-axis-waveUsage
import { TextVariableAxisWave } from "@/components/text-variable-axis-wave";
export function Example() {
return (
<TextVariableAxisWave
as="h1"
minWeight={300}
maxWeight={850}
wavelength={7}
>
Motion shaped one letter at a time
</TextVariableAxisWave>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| children | string | required | Plain text segmented into graphemes and exposed once as the accessible name. |
| as | ElementType | "span" | Semantic wrapper element. Render the wave as the block, for example "h1", or add className="block", for line breaks that never move; an inline span keeps its paragraph's natural layout. |
| trigger | "loop" | "in-view" | "hover" | "loop" | Ambient, once-in-view, or fine-pointer and keyboard-focus recipe. |
| minWeight | number | 320 | Minimum weight, rounded and clamped to 100–700. |
| maxWeight | number | 780 | Maximum weight, rounded and clamped to 300–900. |
| wavelength | number | 6 | Graphemes per wave crest, rounded and clamped to 2–12. |
| speed | number | 1.8 | Seconds per character pulse, clamped to 0.7–4. |
| lift | number | 4 | Crest travel in CSS pixels, clamped to 0–12. |
| direction | "forward" | "reverse" | "forward" | Order in which the crest reaches the graphemes. |
| className | string | undefined | Additional classes merged onto the wrapper. |
Customisation
Shape the effect through props rather than editing the preview wording. Set the font-weight range, crest wavelength, pulse speed, vertical lift and direction to fit the scale and tempo of your own headline. A variable font with a wght axis gives the smoothest result, while the numeric font-weight fallback keeps the effect visible with ordinary fonts.
<TextVariableAxisWave
trigger="hover"
minWeight={260}
maxWeight={880}
wavelength={5}
speed={1.4}
lift={8}
direction="reverse"
>
Give every headline its own rhythm
</TextVariableAxisWave>Frequently asked questions
- Is Variable Axis Wave free for commercial use?
- Yes. Variable Axis Wave is MIT licensed, so you can use it in personal and commercial React and Next.js projects without attribution.
- Do I need a variable font?
- No. A font with a wght axis produces the smoothest interpolation, but every grapheme also receives a matching numeric font-weight fallback so the wave remains visible with ordinary font families.
- Does Variable Axis Wave work with a strict Content Security Policy?
- Yes, with one difference. The component places its line breaks before the first paint with a small inline script. If your policy blocks inline scripts, allow it by nonce or hash, or expect the breaks to apply at hydration instead, which can re-wrap the heading once on the first client paint. Everything else works the same.
- How does Variable Axis Wave handle reduced motion?
- With reduced motion enabled, the component starts no animation. Every grapheme stays readable at the midpoint weight with no vertical transform. Switching reduced motion on while the wave moves stops it at that same midpoint. Strings over 40 graphemes use the same complete static fallback.