Text✨ Premium

Casual Morph

Pure CSS animation: Recursive morphs across its CASL, MONO, and wght axes in 3 distinct states — living typography with zero JavaScript.

CSSRecursiveMulti-axes

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Text has 56 effects, including 4 free. Explore the category →

3 usage examples

Hybrid dev/design agency — live register pivot — Casual Morph example 1

① Hybrid dev/design agency — live register pivot

WhenThe landing page of an agency spanning both development and graphic design uses Recursive to embody its dual identity: the same word or slogan shifts visually between the technical register (MONO 1, geometric strokes) and the creative register (CASL 1, casual curves), making the bipolar positioning instantly perceptible.
WhyRecursive is the only variable font whose CASL and MONO axes semantically encode the code/design opposition: MONO 1 = fixed-pitch, machine precision; CASL 1 = handwriting curves, spontaneity. This duality is not simulated — it is built into the font's structure, making the register pivot narratively justified where any other variable font would offer only a plain weight shift.
Settingsfont-size: 4–5 rem (the word must anchor the composition), duration: 4 s (code default), colors: rgba(255,255,255,.7) → #ec4899 → #8b5cf6 — the chromatic progression amplifies the register shift. Add two micro-tags '< dev >' (MONO 1) and '[ design ]' (CASL 1) as bipolar visual guides; remove the numeric axis badges.
Code editor header — live Recursive font preview — Casual Morph example 2

② Code editor header — live Recursive font preview

WhenA developer tool, online IDE, or theme configurator offers a preview of Recursive with its variable axes in a typography settings panel.
WhyRecursive is designed for both code and display: showing the MONO 1→0 transition in an editor context makes the use case instantly readable to developers. The axis badges reinforce the technical credibility of the component.
Settingsfont-size: 1.5–2.5 rem, duration: 3 s, palette: white / cyan / purple. Keep the badges — they reinforce the configuration context.
Type specimen — full-screen showcase of a variable font — Casual Morph example 3

③ Type specimen — full-screen showcase of a variable font

WhenA type foundry or typography resource site presents Recursive with a live axis demonstration — a type specimen page replacing traditional static layout sheets.
WhyNo static image conveys a variable font as effectively as watching it morph in real time: CASL, MONO, and wght cover the full expressive range in a single element.
Settingsfont-size: 5–7 rem, duration: 6–8 s, extended ease-in-out, single color (white) for an elegant neutral look on a dark background.

How it works

font-variation-settings is a numeric CSS property: the rendering engine interpolates it through @keyframes exactly like opacity or transform. The vfCasualMorph animation defines three stops (0%/100%, 33%, 66%) for three simultaneous axes, looping with ease-in-out over 4 seconds — without a single line of JavaScript.

Each Recursive axis controls a distinct dimension of the letterform: CASL (0 = linear mechanical strokes → 1 = rounded, bouncy handwriting shapes), MONO (1 = fixed-width code spacing → 0 = proportional display spacing), wght (400 Regular → 700 Bold → 900 Black). Combined, the three axes shift the typeface between three personalities: strict light monospace, casual bold proportional, and heavy semi-mono black — opposite typographic registers from a single family.

The color property is co-animated in the same keyframes to visually amplify each state: muted white at the neutral state (mono, regular), pink #ec4899 at the casual state (handwriting, bold), purple #8b5cf6 at the heavy state (semi-mono, black). The browser interpolates hex values as RGB tuples — no extra code required.

The auxiliary elements (.vf-casual-label, .vf-casual-axis) hold "MONO" 1 fixed on their font-variation-settings: their character widths stay constant, serving as a stable visual reference against the morphing main text.

Accessibility

  • prefers-reduced-motion: absent from the code — the CSS animation loops unconditionally. Add @media (prefers-reduced-motion:reduce) { .vf-casual-text { animation: none } } to respect the system preference.
  • No aria-* attributes on the elements: for decorative use, add aria-hidden="true" to .vf-casual-scene; for informative use (meaningful text), wrap it in a role="img" with an aria-label describing the displayed word.
  • Contrast of the three colors against #0a0a0f: muted white (~7:1), pink #ec4899 (~6:1), purple #8b5cf6 (~5:1) — all ≥ 4.5:1, WCAG AA met.
  • The effect is purely decorative and non-interactive: no focusable element, no keyboard navigation requirement.
  • The .vf-casual-axis badges provide plain-text context about the animated axes, but carry no explicit aria role — add aria-hidden="true" if they are purely illustrative.

Browser compatibility

Requires animatable font-variation-settings (CSS Fonts Level 4) and the Recursive variable font. Zero JS dependency.

Chrome 62+✓ Full
Firefox 62+✓ Full
Safari 11+✓ Full
Edge 79+✓ Full
Mobile iOS 11+✓ Full
Android Chrome 62+✓ Full

Without Recursive loaded, the browser falls back to <code>monospace</code> — weight and color animations still run, but the CASL and MONO axes have no visible effect on the glyphs.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="vf-preview">
  <div class="vf-casual-scene">
    <span class="vf-casual-text">Recursive</span>
    <span class="vf-casual-label">CASL · MONO · wght</span>
    <div class="vf-casual-axes">
      <span class="vf-casual-axis">CASL 0→1</span>
      <span class="vf-casual-axis">MONO 0→1</span>
      <span class="vf-casual-axis">wght 400→900</span>
    </div>
  </div>
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
animation-duration on `.vf-casual-text` 4s Duration of one full cycle. 2 s = brisk and sharp, 8 s = slow and contemplative.
font-size on `.vf-casual-text` 2rem Size of the morphing text. Scale up to 4–6 rem for a hero headline, down to 1 rem for a compact label.
color in `@keyframes vfCasualMorph` (3 values) rgba(255,255,255,.7) / #ec4899 / #8b5cf6 Palette of the three states: neutral, casual, heavy. Replace with your brand colors, or lock to a single value to disable the color transition.
CASL in the 3 keyframes 0 / 1 / 0.5 Handwriting register at each state. 0 = pure geometric, 1 = full casual. Narrow to 0→0.5 for a subtle variation.
MONO in the 3 keyframes 1 / 0 / 0.5 Monospace ↔ proportional shift. Fix MONO 1 throughout for a code-only display; fix MONO 0 for display-only.
wght in the 3 keyframes 400 / 700 / 900 Weight range between states. Narrow to 300–600 for a subtler effect; 900 (Black) maximizes the mass contrast between states.
animation-timing-function on `.vf-casual-text` ease-in-out linear = continuous morph with no plateau; cubic-bezier(0.4,0,0.2,1) = sharp acceleration at transitions, marked pause at states.

FAQ

Yes. The effect declares font-family: Recursive, monospace but does not load the font — add <link href="https://fonts.googleapis.com/css2?family=Recursive:slnt,wght,CASL,CRSV,MONO@-15..0,300..1000,0..1,0..1,0..1&display=swap" rel="stylesheet"> in your <head>. For offline or GDPR-compliant use, self-host the woff2 file via fontsource.
Yes. Recursive also exposes CRSV (Cursive 0/1, activates cursive ligatures) and slnt (Slant -15°→0°, italic lean). Add them to font-variation-settings in each keyframe: "CRSV" 0, "slnt" -12. Important: if an axis is omitted from a keyframe, it snaps back to its default instead of interpolating — list all axes in every stop for a consistent morph.
Yes — a pure CSS animation keeps running off-screen (the browser may throttle it in inactive tabs). To reduce CPU use, pause it with an IntersectionObserver: el.style.animationPlayState = isVisible ? 'running' : 'paused'. This has no visual glitch — the animation resumes exactly where it left off.