beta

Particle Text <mv-particle-text>

Text drawn by thousands of canvas particles sampled from the element's own font: they sweep into place left to right, flee the pointer and spring back, blow apart on click, and flow across to the new word when the text changes, colored by a gradient they travel through.

CategoryAnimated text
TypeWeb Component (<mv-particle-text>)
Statusbeta
Keywordssafe-rewrite, particles, canvas, text, morph, interactive, hero, repel

When to use

  • A hero title should be drawn by particles that assemble, flee the cursor and explode on click
  • A short word should morph into another with particles flowing into place

Avoid when

  • Low-end mobile or several instances per page; thousands of canvas particles are costly
  • Long text or paragraphs; the canvas is built for a short word or title and shrinks overflowing text
  • A changing number should be drawn in particles → use Particle Counter instead

Install

node scripts/add.mjs particle-text --out ./src/marvelous

AI agent with the Marvelous UI MCP server: install_components({ slugs: ["particle-text"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Files copied (dependencies included): tokens/tokens.css, core/base.css, core/canvas.js, core/dom.js, core/element.js, core/motion.js, core/observe.js, components/particle-text/particle-text.js, components/particle-text/particle-text.css.

Usage

Canonical markup, to start from and customize with attributes, data-* and CSS variables:

<div style="display:grid;gap:.5rem;width:100%;justify-items:center">
  <mv-particle-text id="mv-particle-text-demo" colors="var(--mv-accent), oklch(0.72 0.14 225), oklch(0.75 0.16 345)">Hello</mv-particle-text>
  <div style="display:flex;gap:.5rem;flex-wrap:wrap;justify-content:center">
    <button class="mv-button" data-size="sm" data-variant="outline" data-word="Hello">Hello</button>
    <button class="mv-button" data-size="sm" data-variant="outline" data-word="Marvelous">Marvelous</button>
    <button class="mv-button" data-size="sm" data-variant="outline" data-word="Thanks!">Thanks!</button>
    <button class="mv-button" data-size="sm" data-variant="ghost" id="mv-particle-text-demo-burst">Burst</button>
  </div>
  <p style="margin:.25rem 0 0;font-size:.8rem;color:var(--mv-fg-subtle)">Hover the letters to scatter them, click to make them explode.</p>
  <mv-particle-text text="2026" style="font-size:3.5rem;max-width:320px;margin-top:.5rem" radius="50"></mv-particle-text>
</div>
<script type="module">
  const pt = document.getElementById("mv-particle-text-demo");
  for (const b of document.querySelectorAll("[data-word]")) b.addEventListener("click", () => { pt.text = b.dataset.word; });
  document.getElementById("mv-particle-text-demo-burst").addEventListener("click", () => pt.burst());
</script>

API

Attributes

NameTypeDefaultDescription
textstringText (otherwise the element content). Changing it triggers a morph.
colorslist of CSS colorsvar(--mv-particle-text-color)One color, or several for a left-to-right gradient laid over the text; particles take the color of where they are (tokens accepted, they follow the theme).
triggerview | hover | load | manualviewWhen the particles assemble (manual: call assemble()).
gapnumberauto (2-8)Sampling step in px (smaller = more particles, capped at 7,000; auto keeps the count under the cap).
sizenumbergap × 0.86Particle side length (px).
radiusnumber90Cursor influence radius (px).
strengthnumber1Repulsion strength.

Methods

NameDescription
assemble()Assembles the particles into text.
burst(x?, y?)Explodes the particles from a point (local coordinates, center by default).

Events

NameDescription
mv-settleAll particles are back in place (the loop stops).

CSS variables

NameDefaultDescription
--mv-particle-text-colorvar(--mv-fg)Default color.

Accessibility

The text stays available as visually hidden text for screen readers; the canvas is aria-hidden. Font family, weight and size come from the element's CSS (the text shrinks if it overflows). Reduced motion: text is assembled immediately, with no repulsion or explosion. Forced colors: particles use the system text color. The loop pauses off-screen and stops when idle.