Ribbon Text

ProTypographypowered by @vectorvesper/motion

Kinetic typography where each letter is extruded into stacked colour bands that lag behind the scroll, so the word stretches and springs like a ribbon. Five palettes ship with it, or pass your own — the array sets the band count. Finds its own scroll container, so it works on the page or inside a nested one.

Ribbon Text

Included with membership

Members install Ribbon Text with one command and get the full source, the usage code and the Framer remix, licensed for client work. One membership covers the whole library and the CLI.

When to use it

A section heading that is the artwork, not a label — a chapter break, a landing statement, a run of words the visitor scrolls through one at a time. It wants a tall section and empty space around it, because the bands spread well past the letterforms. It is the wrong tool for a heading someone needs to read quickly.

  • Give the section far more height than the text needs — around 140vh — so the spread has room to open and close as it travels through.
  • Turn showTopToggles off in production. The palette buttons are a demo affordance, and they are on by default.
  • Keep the word short. Every letter is drawn once per colour band, so a long string multiplies quickly.
  • Pass your own colours when a preset does not fit — an odd count keeps the baseline band centred.
  • Load the font. The default stack starts with Anton, which is a webfont and is not bundled; without it you get Impact.

Avoid:

  • Do not use it for a heading that has to be read at a glance. At the extremes of the spread the letters are deliberately hard to resolve.
  • Do not put two in the same viewport. They both react to the same scroll and the competing springs read as noise.
  • Do not substitute a light or wide typeface. The bands are stacked copies of the letterforms, so thin strokes separate into stripes rather than reading as one ribbon.
  • Do not set scrollProgress while leaving scrollDriven on; the scroll wins and your value is ignored.

Props

A word built from stacked colour bands that stretch apart as it crosses the viewport and spring back when the scroll settles. The bands lag behind one another, so the letters read like a ribbon being pulled rather than a stack of copies.

<RibbonText />

The word and its bands. Sizes itself from fontSize rather than its container, so give it room rather than a fixed box.

PropTypeDefaultDescription
textstring"MUSIC"The word. Short is better — cost scales with letters times bands.
paletteRibbonPaletteName | string[]"monochrome"One of the five presets, or your own colours ordered top to bottom. The array sets the band count — any length works, and the middle entry is the baseline the rest spread away from. An odd number keeps that baseline centred.
fontSizenumber160Base type size in pixels. The bands are positioned relative to this, so it scales the whole effect.
fontFamilystring"Anton, Impact, 'Arial Black', sans-serif"Type stack for the word. Anton is a webfont and is not bundled — load it yourself or the stack falls through to Impact. Whatever you substitute, keep it heavy and condensed: the bands are stacked copies of the letterforms, so a light face leaves gaps between them instead of reading as one ribbon.
fontWeightnumber | string900Weight for the word. Only useful with a variable or multi-weight family — the default face has a single weight.
letterSpacingnumber42Gap between letters in pixels. Wider spacing keeps the bands from colliding at full spread.
wordSpacingnumber70Gap between words in pixels, when the text contains more than one.
spreadMultipliernumber1.0Resting distance between the bands, before any scroll movement is added. This is also the value the ribbon holds under reduced motion.
scrollDrivenbooleantrueWhether scroll position and velocity drive the spread. Turn it off to hold the ribbon still or to drive it yourself.
scrollProgressnumber—Drive the spread yourself with a value from 0 to 1. Only read when scrollDriven is off.
showTopTogglesbooleantrueRenders palette-switching buttons above the word. On by default and meant for demos — turn it off in production.
onPaletteChange(palette: RibbonPaletteName) => void—Fires when a palette is chosen through those buttons.
classNamestring""Applied to the wrapper.
styleReact.CSSProperties{}Inline styles merged onto the wrapper.
mount"auto" | "eager""auto"When to start the ribbon. The default waits until the page has frame headroom to spare. The bands stay extruded regardless — that is the typography, not the animation — so only the springing waits. Set "eager" to have it running from the first frame. Note this defaults the opposite way from the components that build a 3D scene: those start eagerly and take "auto" to defer. Both mean the same thing by "auto" — let the runtime pick the moment — the default just matches what each component already did. Neither setting takes it off the shared frame loop or overrides the reduced-motion preference.

Presets

PresetDescriptionKey Overrides
"monochrome"Greys falling to ink at the centre and back out again. The default, and the one that survives any background.—
"electric80s"Cyan and cobalt above, magenta through crimson below, black at the baseline. The loudest of the five.—
"pacific"Ocean blues and teal on top, coral and berry underneath — cool above the line, warm below.—
"desert"Slate, olive and sage over tan, terracotta and burnt brick. The most muted; good under body copy.—
"amberGold"Navy and jade above, amber and flame below. Reads as metal more than as colour.—

<PALETTES />

type

The five presets, keyed by name: monochrome, electric80s, pacific, desert and amberGold. Each carries a display name, its seven colours and the index of the baseline band — useful for building your own palette picker.

It reads the visitor's reduced-motion preference and holds the ribbon at its resting spread — the bands stay extruded, because that is the typography rather than the animation, but the springing and the per-letter wave stop.
Every letter is rendered once per colour band, so the element count is the word's length times seven. A short word is cheap; a sentence is not. It animates transforms only, so what it costs is layout weight rather than paint.
Client component. It measures its own position against the viewport, so it waits until it is safe to mount before it starts.
It draws on the shared frame loop rather than starting one of its own, so putting several animated components on a page costs one callback, not one each, and the scheduler can set decorative work aside when frames get tight. Its position in the scroller is measured before any component writes for that frame, so tracking the scroll costs nothing beyond the read itself.
Back to all components