Letter Swap <mv-letter-swap>

Giro letra a letra para enlaces de navegación y botones: al pasar el puntero o al recibir el foco de teclado, cada letra se aleja girando para revelar a su gemela, en cascada, o gira como un pequeño cubo 3D. Las letras reales permanecen en el DOM y en sus propias cajas fijas, así que la etiqueta nunca se recoloca, se ajusta entre palabras, sigue siendo seleccionable y puede cambiar en tiempo de ejecución.

CategoríaTexto animado
TipoWeb Component (<mv-letter-swap>)
Estadoestable
Keywordsletter, swap, roll, flip, 3d, hover, nav, button, stagger, accessible

When to use

  • Nav links or button labels should roll their letters on hover and keyboard focus
  • A 3D cube flip of each letter suits a playful or technical brand
  • A short label should roll once when it enters the viewport, on page load or on demand
  • The revealed letters should take an accent color while a control is hovered

Avoid when

  • A different label must roll in on hover, or a highlighter should glide between phrases → use Rolling Text instead
  • Long sentences or paragraphs; the per-letter roll is meant for short labels
  • The label is underlined; per-letter boxes do not carry the parent text decoration

Instalación

node scripts/add.mjs letter-swap --out ./src/marvelous

Agente de IA con el servidor MCP de Marvelous UI: install_components({ slugs: ["letter-swap"], 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/letter-swap/letter-swap.js, components/letter-swap/letter-swap.css.

Uso

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

<div style="display:grid;gap:2.25rem;justify-items:center;text-align:center">
  <nav aria-label="Demo" style="display:flex;gap:.25rem;padding:.35rem;border:1px solid var(--mv-border);border-radius:999px;background:var(--mv-surface);box-shadow:var(--mv-shadow-sm)">
    <a href="#" style="padding:.45rem .9rem;border-radius:999px;color:var(--mv-fg);text-decoration:none;font:500 .9rem var(--mv-font-sans)"><mv-letter-swap>Work</mv-letter-swap></a>
    <a href="#" style="padding:.45rem .9rem;border-radius:999px;color:var(--mv-fg);text-decoration:none;font:500 .9rem var(--mv-font-sans)"><mv-letter-swap stagger-from="center">Studio</mv-letter-swap></a>
    <a href="#" style="padding:.45rem .9rem;border-radius:999px;color:var(--mv-fg);text-decoration:none;font:500 .9rem var(--mv-font-sans)"><mv-letter-swap stagger-from="random">Journal</mv-letter-swap></a>
    <a href="#" style="padding:.45rem .9rem;border-radius:999px;background:var(--mv-fg);color:var(--mv-bg);text-decoration:none;font:500 .9rem var(--mv-font-sans)"><mv-letter-swap direction="down">Contact</mv-letter-swap></a>
  </nav>

  <p style="margin:0;font:800 clamp(2.2rem,6vw,3.8rem)/1 var(--mv-font-sans);letter-spacing:-.045em;text-transform:uppercase">
    <mv-letter-swap variant="flip" trigger="view" loop stagger="45" duration="700">Creative</mv-letter-swap>
    <span style="color:var(--mv-fg-muted)">by nature</span>
  </p>

  <div style="display:flex;gap:.75rem;flex-wrap:wrap;justify-content:center">
    <button class="mv-button" data-size="lg"><mv-letter-swap variant="flip" stagger="30">Start for free</mv-letter-swap></button>
    <button class="mv-button" data-size="lg" data-variant="outline"><mv-letter-swap stagger="15" duration="420" easing="out" style="--mv-letter-swap-twin:var(--mv-accent)">Watch the demo</mv-letter-swap></button>
  </div>
</div>

API

Attributes

NameTipoDefaultDescription
textstringLabel to roll. Defaults to the element text; changing it rebuilds the letters.
variantslide | flipslideslide: the letter slides; flip: a rotating 3D cube.
triggerhover | view | load | manualhoverhover follows the parent link or button (mouse or pen hover, keyboard focus); view and load roll once; manual waits for replay().
directionup | downupRoll direction: up (the twin rises from below) or down.
durationnumber500Duration per letter (ms).
staggernumber22Delay between letters (ms).
stagger-fromstart | center | end | randomstartWhere the cascade starts.
delaynumber0Initial delay (ms).
easingCSS easingvar(--mv-ease-in-out)Easing curve: any CSS easing, or a token name (out, in, in-out, emphasized, spring, bounce, snappy).
loopbooleanWith view, load or manual: keeps rolling while visible, pausing 1.6 s between rolls.

Properties

NameTipoDescription
labelstringRead only. The label as announced to screen readers.

Methods

NameDescription
replay()Rolls every letter once and settles back invisibly. Returns a Promise resolved when done (at once under reduced motion).

CSS variables

NameDefaultDescription
--mv-letter-swap-leading1.2Unitless line height of each letter box (the clipping window). The extra leading is cancelled, so the line keeps its height.
--mv-letter-swap-easingvar(--mv-ease-in-out)Default easing curve.
--mv-letter-swap-twincurrentColorColor of the revealed twin while hovered or focused (a replay always lands on identical letters).
--mv-letter-swap-perspective20emPerspective depth (flip variant).

Accessibility

Screen readers get the label once, as plain text; the rolling letters are aria-hidden, so there is no per-letter reading. The real letters stay in the DOM (selectable, copied once) and the twin is drawn by CSS. Hover follows the parent control and its keyboard :focus-visible; touch does not trigger it. Each letter keeps a fixed box, so nothing reflows. Reduced motion, print and forced colors show static text.

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