Dispersed Text

v1.1.1
FreeTypography

An advanced interactive typography component where text morphs liquidly on content change and disperses into custom particle fields (flow, explode, attract, wind, gravity, vortex) on interaction. Supports solid or gradient colors, speed-based color mapping, neon glow, custom shapes, and manual animation triggers.

Dispersed Text

Installation

Step 1. Initialize ProjectOne-time setup

Run once in your project root to configure paths and dependencies:

npx vectorvesper init
Step 2. Add ComponentInstalls source & assets

Downloads and registers the component in your project:

npx vectorvesper add dispersed-text
Step 3. Required DependenciesAuto-installed with CLI

If installing manually or managing your package lock:

npm install framer-motion
Packages:framer-motion

Usage

Import

import { DispersedText } from "@/components/vv/dispersed-text";

Usage

<DispersedText />
This component respects prefers-reduced-motion and provides a graceful fallback.

Props

Text rendered as a cloud of particles that scatters when you touch it and reassembles when you leave. Six ways for it to come apart, four particle shapes, and colour that can follow speed.

<DispersedText />

The whole block. Sizes itself to its container.

PropTypeDefaultDescription
text*stringThe words to render. Required.
interactionMode"flow" | "explode" | "attract" | "vortex" | "wind" | "gravity""flow"How the particles react. Flow pushes them gently aside, explode throws them outward, attract pulls them in, vortex spins them around the pointer, wind blows them along windAngle, gravity drops them.
trigger"hover" | "click" | "manual""hover"What sets it off. With `manual` the component ignores the pointer and you drive it through isDispersed or progress.
isDispersedbooleanfalseScattered or assembled, when trigger is `manual`.
progressnumber0Position between assembled and scattered, 0–1, when you want to drive it from a scroll or timeline rather than a boolean.
windAnglenumber0Direction the wind blows, in degrees. Only used by the `wind` mode.
springnumber0.08How hard particles are pulled back to their letter. Higher snaps back; lower drifts home slowly.
meltSpeednumber0.06How quickly they leave the letter when disturbed.
swirlnumber1.2How much they curl around the pointer rather than moving straight away from it.
swirlRadiusnumber160How far from the pointer, in pixels, that influence reaches.
streaknumber5Length of the trail each particle leaves. 0 draws them as points.
turbulencenumber1Random jitter layered over the motion. 0 turns the jitter off.
fontSizenumberfitted to the boxFixed size in pixels. Left unset, the text is fitted to its container.
fitWidthnumber0.82Fraction of the container width the text should fill when it is being fitted.
fontFamilystringthe system sans stackFace used to lay out the letters before they become particles.
fontWeightnumber | string700Weight of that face. Heavier gives more particles to work with.
lineHeightnumber1.15Line spacing for multi-line text.
gapnumber6Spacing between sampled particles, in pixels. Lower packs them denser and costs more to draw.
dotSizenumber1.7Size of one particle.
shape"circle" | "square" | "triangle" | "star""circle"What each particle is drawn as.
colorstring"#7EACB5"Particle colour when colorMode is `solid`.
colorMode"solid" | "gradient" | "speed""solid"One colour, a gradient across the text, or colour driven by how fast each particle is moving.
gradientColorsstring[]["#7EACB5", "#E07B9B"]Colours the gradient runs through, when colorMode is `gradient`.
gradientDirection"horizontal" | "vertical" | "diagonal""horizontal"Which way that gradient runs.
speedColorstring"#FF007A"Colour a particle reaches at full speed, when colorMode is `speed`. It blends toward this from `color`.
glowbooleanfalseAdds a bloom around each particle.
glowColorstringthe particle colourColour of that bloom. Follows the particle when unset.
glowBlurnumber8Spread of the bloom in pixels.
maxDprnumber2Pixel-ratio ceiling. Lower it to 1 on particle-heavy layouts to halve the fill cost.
debugbooleanfalseDraws the sampling grid over the text, for tuning gap and dotSize.
classNamestring""Applied to the wrapper.
styleReact.CSSPropertiesInline styles merged onto the wrapper.
aria-labelstringthe text propLabel announced for the block. Falls back to `text`, so it is usually already correct.

Examples

Scatter on hover
import DispersedText from "@/components/vv/dispersed-text/DispersedText";

<div className="h-64">
  <DispersedText text="DISPERSE" />
</div>
Blown sideways, coloured by speed
<DispersedText
  text="WINDWARD"
  interactionMode="wind"
  windAngle={30}
  colorMode="speed"
  color="#7EACB5"
  speedColor="#FF007A"
  streak={12}
/>
Driven by scroll instead of the pointer
<DispersedText
  text="ASSEMBLE"
  trigger="manual"
  progress={scrollProgress}
  interactionMode="gravity"
/>
The particles keep moving under reduced motion. Set trigger="manual" with progress={0} when the preference is set — the text stays fully assembled and readable, with no motion at all.
Cost scales with particle count, which gap controls: halving it roughly quadruples the work. If a long line feels heavy, raise gap before touching anything else, and drop maxDpr to 1.
Client component. It measures text and draws to a canvas, neither of which exists on the server.

Source

/**
 * Dispersed Text — Vector Vesper
 * https://vectorvesper.dev/components
 *
 * Copyright (c) 2026 Vector Vesper
 * Released under the MIT License. This notice must be retained in copies and
 * substantial portions of the file. https://vectorvesper.dev/license
 */
export { default as DispersedText } from "./DispersedText";
export type { DispersedTextProps } from "./DispersedText";
View on GitHubReport an issue