Rolling Text <mv-rolling-text>

Etiqueta de hover que sube rodando letra a letra, de izquierda a derecha, y vuelve a aterrizar o hace aterrizar una segunda etiqueta desde abajo, con un ligero desenfoque en tránsito y una huella fijada a la etiqueta más larga; variante glide: un marcador se desliza entre las frases de una oración. El texto real sigue siendo un único nodo de texto sencillo, seleccionable y traducible.

CategoríaTexto animado
TipoWeb Component (<mv-rolling-text>)
Estadoestable
Keywordstext, roll, slot, cascade, hover, letters, stagger, blur, label-swap, highlight, marker, glide, nav, button, accessible

When to use

  • Nav links or buttons should roll their label on hover or keyboard focus without any layout shift
  • A call to action should swap in a short second label, like Start writing to It’s free
  • A marker should glide between key phrases of a sentence on hover or on its own
  • A sold or public page needs hover text that stays selectable, findable and translatable

Avoid when

  • A 3D cube flip per letter or a single roll on view or load is wanted → use Letter Swap instead
  • The swapped label carries needed information; screen readers never get it and touch never reveals it

Instalación

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

Agente de IA con el servidor MCP de Marvelous UI: install_components({ slugs: ["rolling-text"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Archivos copiados (dependencias incluidas): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/motion.js, core/observe.js, components/rolling-text/rolling-text.js, components/rolling-text/rolling-text.css.

Uso

Inicio rápido, el marcado mínimo que funciona:

<a href="/work"><mv-rolling-text hover-text="View all">Work</mv-rolling-text></a>

Marcado de referencia: parte de él y personalízalo con atributos, data-* y variables CSS:

<div style="display:grid;gap:2.5rem;width:100%;max-width:44rem">
  <nav aria-label="Main" style="display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:1rem;padding:.625rem .75rem .625rem 1.25rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-full);background:var(--mv-surface);box-shadow:var(--mv-shadow-sm)">
    <strong style="font-size:.9375rem;letter-spacing:-.01em">Fieldnote</strong>
    <ul style="display:flex;gap:1.5rem;margin:0;padding:0;list-style:none;font-size:.875rem;font-weight:500">
      <li><a href="#product" style="color:inherit;text-decoration:none"><mv-rolling-text>Product</mv-rolling-text></a></li>
      <li><a href="#customers" style="color:inherit;text-decoration:none"><mv-rolling-text hover-text="Stories">Customers</mv-rolling-text></a></li>
      <li><a href="#pricing" style="color:inherit;text-decoration:none"><mv-rolling-text hover-text="From $12">Pricing</mv-rolling-text></a></li>
    </ul>
    <a href="#signup" class="mv-button" data-variant="primary" style="border-radius:var(--mv-radius-full)"><mv-rolling-text hover-text="It’s free →" style="--mv-rolling-text-align:center">Start writing</mv-rolling-text></a>
  </nav>

  <a href="#changelog" style="justify-self:start;color:inherit;text-decoration:none;font-size:clamp(2.25rem,6vw,3.5rem);font-weight:650;letter-spacing:-.035em;line-height:1">
    <mv-rolling-text hover-text="Read the changelog" stagger="22" style="--mv-rolling-text-leading:1.08">What’s new in 4.0</mv-rolling-text>
  </a>

  <mv-rolling-text variant="glide" autoplay="2600" style="font-size:clamp(1.25rem,3vw,1.625rem);line-height:1.45;letter-spacing:-.015em;color:var(--mv-fg-muted);max-width:36rem">
    Fieldnote keeps <mark>every draft in sync</mark>, turns meetings into <mark>searchable notes</mark> and ships <mark>offline by default</mark>, so your team can <mark>write from anywhere</mark>.
  </mv-rolling-text>
</div>

API

Attributes

NameTipoDefaultDescription
variantroll | gliderollroll: rolling letters; glide: a marker gliding between <mark> elements.
hover-textstringroll: label that lands on hover (the same text otherwise). The width is that of the longer label from the start.
triggerhover | manualhoverhover follows the parent link / button (mouse or pen hover and keyboard focus, never touch); manual: driven by active.
activebooleanForces the rolled state.
staggernumber28Delay between letters (ms).
durationnumber520Duration per letter (ms).
autoplayboolean | numberglide: the marker moves to the next phrase on its own (ms, 2800 by default, 800 minimum), paused on hover, focus, off-screen, in hidden tabs and with reduced motion.

Properties

NameTipoDescription
indexnumberglide: highlighted phrase.

Methods

NameDescription
select(index)glide: highlights a phrase and makes it the resting position.

Events

NameDescription
mv-changeglide: the marker moved to another phrase. detail: { index, phrase }.

Content structure

NameDescription
<mark> / [data-phrase]glide: highlightable phrases (data-active = starting phrase). Links inside stay focusable and move the marker.

CSS classes

NameDescription
mv-rolling-text-textroll: wrapper of the real text (your original nodes, unchanged).
mv-rolling-text-stage / -line / -charroll: generated aria-hidden letter stage, shown only while rolling or while a different label is up.
mv-rolling-text-marker / -phraseglide: marker pieces (one per line of a wrapped phrase) and phrases.

CSS variables

NameDefaultDescription
--mv-rolling-text-leading1.2Line height, which is also the height of the rolling window.
--mv-rolling-text-easingcubic-bezier(0.7, 0, 0.2, 1)Roll easing curve.
--mv-rolling-text-blur3pxBlur of letters in transit.
--mv-rolling-text-alignstartAlignment when the two labels differ in length (start, center, end).
--mv-rolling-text-markeraccent 22%glide: marker background.
--mv-rolling-text-marker-radius0.3emglide: marker corner radius.
--mv-rolling-text-active-fgvar(--mv-fg)glide: color of the highlighted phrase.

Accessibility

roll: your text stays a plain, visible text node inside the element, so screen readers, the parent’s accessible name, selection, find-in-page and page translation get it unchanged. While rolling it is only made transparent and the letters are painted as generated content on an aria-hidden, non-selectable stage placed on the measured position of each letter, so nothing jumps when the roll starts or ends. The second label is decorative: the accessible name stays the original text. Hover follows the parent’s keyboard focus (:focus-visible) and ignores touch. With reduced motion (system or data-motion="reduce") the text never moves: without hover-text nothing happens, with it the label swaps instantly. Printing shows the real text. glide: <mark> elements keep their semantics and links inside stay focusable; the marker is aria-hidden and becomes a Highlight outline in forced colors; autoplay stops with reduced motion, off-screen and in hidden tabs.

Esta página se tradujo con IA. Informar de un problema de traducción