Text Flapper
Solari split-flap display effect with cycling characters and configurable transition types.
Text Flapper animates text like a mechanical Solari departure board — each character cycles through random intermediate glyphs before landing on its target, with seven built-in transition styles.
Text Flapper automatically applies split: 'chars' when flap is set — you do not need to add it yourself. If you do provide split, it overrides the internal default, letting you reconfigure the split (e.g. split: 'chars,words'). The SDK also auto-adds a default stagger (each: 0, from: 'start') when none is set. The flap config controls the cycling behavior; from/to handle surrounding motion.
Basic Usage
import { Motion } from "@motion.page/sdk";
// split: 'chars' is auto-applied — no need to add it
Motion("board", ".departure-title", {
flap: { type: "board", styledBoard: true },
stagger: 0.05,
duration: 0.8,
}).onPageLoad();
// Explicit split overrides the default (e.g. to also split words)
Motion("board", ".departure-title", {
split: "chars,words",
flap: { type: "board", styledBoard: true },
stagger: 0.05,
duration: 0.8,
}).onPageLoad(); The flap Property
Set flap inside your AnimationConfig. All sub-options are optional.
Motion("reveal", ".hero-text", {
split: "chars",
flap: {
type: "flip",
charset: "alphanumeric",
cycles: [2, 5],
perspective: 400,
},
stagger: 0.04,
duration: 0.6,
}).onPageLoad(); | Option | Type | Default | Description |
|---|---|---|---|
type | FlapType | 'flip' | Transition style for each character — see Transition Types |
charset | CharsetPreset | string | 'alphanumeric' | Character pool for random intermediate glyphs — see Charsets |
cycles | number | [min, max] | [2, 5] | How many random chars to show before landing. Tuple = random per character |
perspective | number | 400 | CSS perspective depth in px. Only affects flip and board types |
styledBoard | boolean | true | Dark gradient tiles with split-line divider. Only applies to board type |
stableWidth | boolean | 'cells' | 'container' | false | Strategy for preventing layout shift during cycling — see stableWidth. Auto-enabled (as 'cells') when board + styledBoard: true |
preserveWhitespaceCells | boolean | false | Render spaces as empty board cells. Only applies to board type |
Transition Types
Seven styles are available via flap.type. The default is 'flip'.
flip — 3D card rotation
Each character rotates on the X axis like a physical flip card. Composable with from/to transforms.
Motion("flip-in", ".headline", {
split: "chars",
flap: { type: "flip", cycles: 6, perspective: 300 },
stagger: 0.05,
duration: 0.8,
}).onPageLoad(); fade — opacity crossfade
The outgoing character fades out, the incoming fades in. fade owns the opacity property — do not set opacity in from/to when using this type.
Motion("fade-decode", ".status-text", {
split: "chars",
flap: { type: "fade", charset: "katakana", cycles: [4, 8] },
stagger: { each: 0.03, from: "random" },
duration: 1,
}).onPageLoad(); slide — vertical slide
Characters slide in and out vertically, clipped by an overflow: hidden parent. Composable with other transforms.
// Slot machine effect
Motion("slot", ".score", {
split: "chars",
flap: { type: "slide", charset: "numeric", cycles: 8 },
stagger: 0.06,
duration: 1.2,
}).onPageLoad(); blur — blur dissolve
Characters blur in and out. blur owns the filter property — do not set filter in from/to when using this type.
Motion("blur-decode", ".classified", {
split: "chars",
flap: { type: "blur", cycles: [3, 6] },
stagger: 0.03,
duration: 0.8,
}).onPageLoad(); scale — scale pop
Characters scale in and out. Composable with other from/to transforms.
Motion("scale-unlock", ".success-text", {
split: "chars",
flap: { type: "scale", cycles: [2, 4] },
stagger: 0.04,
duration: 0.7,
}).onPageLoad(); board — Solari departure board
Full mechanical split-flap effect: each character element is restructured into layered panels that fold in half. The most realistic — use with styledBoard: true for the complete dark-tile look.
// Styled departure board
Motion("departures", ".board-title", {
split: "chars",
flap: { type: "board", styledBoard: true, perspective: 400 },
stagger: 0.05,
duration: 0.9,
}).onPageLoad();
// Unstyled — inherit your own CSS
Motion("gate", ".gate-code", {
split: "chars",
flap: { type: "board", styledBoard: false },
stagger: 0.04,
duration: 0.7,
}).onPageLoad(); Board type ownership: restructures the character element’s DOM into child panels. The original el.textContent is cleared; text lives in the generated sub-elements.
none — text cycling only
No built-in visual transition. Characters cycle through the charset without animation. Use this with your own from/to properties to fully control the effect.
// Pure text scramble — visuals come from from/to
Motion("scramble", ".encrypted", {
split: "chars",
flap: { type: "none", charset: "symbols", cycles: [6, 12] },
from: { opacity: 0 },
stagger: 0.02,
duration: 0.6,
}).onPageLoad(); Charsets
The charset option sets the pool of random intermediate characters. Pass a preset name or any string — every character in the string is drawn from at random.
| Preset | Characters |
|---|---|
'alphanumeric' | ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 |
'alpha' | ABCDEFGHIJKLMNOPQRSTUVWXYZ |
'numeric' | 0123456789 |
'hex' | 0123456789ABCDEF |
'binary' | 01 |
'katakana' | Full katakana syllabary (ア–ン) |
'symbols' | !@#$%^&*+-=<>?/|~ |
'blocks' | ░▒▓█▄▀■□▪▫ |
| custom string | Any string — each character is used as a random intermediate |
// Custom charset — Greek letters
Motion("greek", ".formula", {
split: "chars",
flap: { type: "flip", charset: "αβγδεζηθικλμνξοπρστυφχψω" },
stagger: 0.04,
duration: 0.8,
}).onPageLoad();
// Matrix effect
Motion("matrix", ".system-status", {
split: "chars",
flap: { type: "fade", charset: "katakana", cycles: [4, 8] },
stagger: { each: 0.03, from: "random" },
duration: 1,
}).onPageLoad(); Cycles
cycles controls how many random characters appear before landing on the target. Use a number for a fixed count or a [min, max] tuple for a random range per character — the variation makes the effect feel more organic.
// Fixed — every char does exactly 6 cycles
flap: { cycles: 6 }
// Range — each char gets 2–8 random cycles
flap: { cycles: [2, 8] } The builder exposes this as two separate sliders (min / max), range 1–20.
Combining flap with from/to
For flip, slide, scale, and none types, the from/to properties animate once per cycle using a V-curve:
- Phase
0→0.5: value moves fromtotowardfrom(character becoming hidden) - Phase
0.5→1: value moves fromfromback toto(new character revealed)
This means from: { opacity: 0, y: 20 } rides every cycle, not just the first.
// Flip + additional opacity + translate per cycle
Motion("flip-reveal", ".hero-text", {
split: "chars",
flap: { type: "flip", cycles: 4 },
from: { opacity: 0, y: 20 },
stagger: 0.04,
duration: 0.8,
ease: "power2.out",
}).onPageLoad(); Property conflicts to avoid:
| Type | Owned property | Don’t use in from/to |
|---|---|---|
fade | opacity | opacity |
blur | filter | filter |
flip | rotateX (composable) | safe to add other transforms |
slide | translateY (composable) | safe to add other transforms |
scale | scale (composable) | safe to add other transforms |
The Board Type in Detail
type: 'board' restructures each character element into layered sub-elements that replicate the mechanics of a physical split-flap display:
container
├─ staticTop — top half of incoming character (revealed as flap falls)
├─ staticBottom — shows old char bottom until midpoint, then new char
├─ flapWrap — rotates 0°→-180° around the center split line
│ ├─ flapFront — old character top half (visible 0°–90°)
│ └─ flapBack — new character bottom half (visible 90°–180°)
├─ shadow — opacity driven by sin(phase × π) for depth
└─ splitLine — permanent 1px center divider (z-index: 3) styledBoard
When true (the default), applies departure-board tile styling:
- Dark gradient background (
#2a2a2e → #1e1e22top,#1e1e22 → #16161abottom) 4pxborder radius- Text color
#e8e8ec - Split-line color
rgba(0,0,0,0.6) 2pxmargin between tiles
When false, styling is minimal — only a rgba(0,0,0,0.15) split line and 1px margin. Use this to apply your own tile CSS.
preserveWhitespaceCells
By default, spaces and other whitespace are skipped and never cycled. Set preserveWhitespaceCells: true to render them as empty board cells — useful when you need fixed-width rows like a real departure board.
Motion("flight-info", ".flight-row", {
split: "chars",
flap: {
type: "board",
styledBoard: true,
preserveWhitespaceCells: true,
},
stagger: 0.04,
duration: 1,
}).onPageLoad(); stableWidth
Controls how the flap prevents layout shift while random characters cycle through. Each mode trades a different aesthetic for stability.
| Value | Behavior |
|---|---|
false (default) | No pinning. Text width flexes with each rendered glyph. Fine for same-width charsets (Latin → alphanumeric). |
true or 'cells' | Pin each character cell to the widest glyph across the target and the charset pool. Every cell is the same width regardless of which glyph renders at the moment. Great for monospace aesthetics (departure boards, counters). Adds visible spacing around narrow Latin letters when paired with wide charsets (katakana, CJK). |
'container' | Pin the parent element’s outer width only — characters flow naturally inside. Latin letters keep their natural spacing while the surrounding layout (rows, grids) stays rock-steady. Best for inline hover flappers on nav links where cell-pinning would space the letters out unnaturally. |
Automatically enabled (as 'cells') when board + styledBoard: true.
// Cell mode — every cell the same width (classic split-flap look)
Motion("counter", ".score", {
flap: { type: "fade", charset: "numeric", cycles: 8, stableWidth: "cells" },
duration: 1,
}).onPageLoad();
// Container mode — outer width reserved, letters stay naturally spaced
Motion("nav-flap", ".nav-link", {
flap: { type: "fade", charset: "katakana", cycles: [3, 6], stableWidth: "container" },
duration: 0.5,
}).onHover({ each: true, onEnter: "restart", onLeave: "none" }); Perspective
perspective sets the CSS 3D perspective depth in pixels. Only affects flip and board types. Lower values create a more dramatic, compressed 3D effect.
// Dramatic close perspective
flap: { type: "board", perspective: 60, styledBoard: true }
// Subtle, distant perspective (default feel)
flap: { type: "flip", perspective: 400 } Range: 50–1000 px.
Use Cases
Departure Board
Motion("departures", ".board-header", {
split: "chars",
flap: { type: "board", styledBoard: true },
stagger: 0.05,
duration: 0.9,
}).onPageLoad(); Countdown / Score Counter
Motion("score", ".score-value", {
split: "chars",
flap: { type: "slide", charset: "numeric", cycles: 10 },
stagger: 0.08,
duration: 1.2,
}).onPageLoad(); Classified / Decode Reveal
Motion("classified", ".secret-text", {
split: "chars",
flap: { type: "blur", cycles: [3, 6] },
stagger: 0.03,
duration: 0.9,
}).onScroll({ toggleActions: "play none none none", start: "top 80%" }); Cyber / Matrix Aesthetic
Motion("matrix", ".terminal-output", {
split: "chars",
flap: { type: "fade", charset: "katakana", cycles: [4, 8] },
stagger: { each: 0.03, from: "random" },
duration: 1,
}).onPageLoad(); Binary Counter
Motion("binary", ".bit-display", {
split: "chars",
flap: { type: "slide", charset: "binary", cycles: 10 },
stagger: 0.08,
duration: 1.2,
}).onPageLoad(); Blocks Loading Indicator
Motion("loading", ".loading-text", {
split: "chars",
flap: { type: "none", charset: "blocks", cycles: [4, 8] },
stagger: 0.02,
duration: 0.8,
}).onPageLoad(); Stagger with Flapper
Stagger is the primary way to shape the flapper’s cascade feel. All stagger options work — from: 'random' is particularly effective for organic decode effects.
// Left-to-right cascade (default)
stagger: 0.05
// Random order — each char starts at a different time
stagger: { each: 0.03, from: "random" }
// Center outward
stagger: { each: 0.04, from: "center" } Cleanup
The board type restructures the DOM. Use Motion.reset() or .kill() to revert character elements back to their original state.
// Kill one timeline and revert DOM
Motion("departures").kill();
// Revert by target element
Motion.reset(".board-title"); API Reference
interface FlapConfig {
type?: 'flip' | 'fade' | 'slide' | 'blur' | 'scale' | 'board' | 'none';
charset?: 'alphanumeric' | 'alpha' | 'numeric' | 'hex' | 'binary' | 'katakana' | 'symbols' | 'blocks' | string;
cycles?: number | [min: number, max: number];
perspective?: number;
styledBoard?: boolean;
stableWidth?: boolean | 'cells' | 'container';
preserveWhitespaceCells?: boolean;
}
// Used inside AnimationConfig — split: 'chars' is auto-applied when flap is set
interface AnimationConfig {
split?: SplitType; // optional — defaults to 'chars' when flap is present
flap?: FlapConfig;
} Related: Text Splitter (optional — auto-applied by flap) · Stagger · Opacity