Casual Morph
Pure CSS animation: Recursive morphs across its CASL, MONO, and wght axes in 3 distinct states — living typography with zero JavaScript.
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



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, addaria-hidden="true"to.vf-casual-scene; for informative use (meaningful text), wrap it in arole="img"with anaria-labeldescribing 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-axisbadges provide plain-text context about the animated axes, but carry no explicitariarole — addaria-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.
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):
<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>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| 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
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.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.IntersectionObserver: el.style.animationPlayState = isVisible ? 'running' : 'paused'. This has no visual glitch — the animation resumes exactly where it left off.