Text morph

Transition between similarly sized short labels while preserving reading position.

Preview

Live preview

Text that animates character by character when it changes, drawing attention to the new value.

Preview
Build
Authoritative vocabulary

Definition and close-term distinctions

English definitions are preserved verbatim from Animation Vocabulary; translations and distinctions help you choose precisely.

Canonical termtext-morph

Text morph

文字形变
Authoritative definition

Text that animates character by character when it changes, drawing attention to the new value.

Chinese translation

文字变化时逐字符播放动画,把注意力引向新值。

Browse all 91 terms
Design contract

Decide before this motion ships

Purpose, frequency, trigger, timing, interruptibility, and gesture rules shape the implementation.

Purpose

Transition smoothly between similarly sized short text values and direct attention to the change.

Frequency

Use for low- to medium-frequency metrics, status words, and short labels, replacing body paragraphs directly.

Trigger

Trigger on value change and animate only the characters or phrase that changed.

Enter / exit

Soften and replace old glyphs as new glyphs appear on the same baseline, keeping container width predictable.

Interruptibility

Coalesce rapid updates and settle only on the latest text.

Gesture and keyboard

No gesture is required; use an aria-live strategy that avoids character-by-character announcements.

Reduced-motion strategy

Replace the full text immediately and use static highlighting or one short fade to signal change.

Output

Copy into your interface

Current parameters update visual-state CSS, semantic HTML, the required vanilla JS runtime, and an interaction prompt.

Updates with parameters
motion.css
.motion-demo.motion-demo--text-morph {
  position: relative;
  display: grid;
  place-items: center;
  width: min(100%, 34rem);
  min-height: 15rem;
  padding: 2rem;
  overflow: hidden;
  color: #18181b;
  background: #fafafa;
  border: 1px solid #e4e4e7;
  border-radius: 12px;
  isolation: isolate;
}

.motion-demo.motion-demo--text-morph *, .motion-demo.motion-demo--text-morph *::before, .motion-demo.motion-demo--text-morph *::after {
  box-sizing: border-box;
}

.motion-demo.motion-demo--text-morph .motion-surface {
  position: relative;
  display: grid;
  gap: 0.65rem;
  width: min(100%, 18rem);
  padding: 1.25rem;
  background: #ffffff;
  border: 1px solid #e4e4e7;
  border-radius: 8px;
  box-shadow: 0 1px 2px rgb(0 0 0 / 6%), 0 10px 28px rgb(0 0 0 / 7%);
}

.motion-demo.motion-demo--text-morph .motion-line {
  display: block;
  width: 100%;
  height: 0.5rem;
  background: #e4e4e7;
  border-radius: 999px;
}

.motion-demo.motion-demo--text-morph .motion-line:last-child { width: 62%; }

.motion-demo.motion-demo--text-morph .motion-button {
  position: relative;
  min-height: 2.75rem;
  padding: 0.75rem 1.1rem;
  color: white;
  font: inherit;
  font-weight: 700;
  background: #18181b;
  border: 0;
  border-radius: 7px;
  cursor: pointer;
}

.motion-demo.motion-demo--text-morph .motion-button:focus-visible { outline: 3px solid rgb(37 99 235 / 32%); outline-offset: 3px; }

.motion-demo.motion-demo--text-morph .motion-action-row {
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  margin-top: 0.85rem;
}

.motion-demo.motion-demo--text-morph .motion-replay-scene {
  width: min(100%, 22rem);
  display: grid;
  place-items: center;
}

.motion-demo.motion-demo--text-morph .motion-pausable-scene {
  width: min(100%, 22rem);
  display: grid;
  place-items: center;
}

.motion-demo.motion-demo--text-morph button.motion-surface {
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.motion-demo.motion-demo--text-morph .motion-replay-button,
.motion-demo.motion-demo--text-morph .motion-undo-button,
.motion-demo.motion-demo--text-morph .motion-dismiss-button,
.motion-demo.motion-demo--text-morph .motion-pause-button {
  min-height: 2.75rem;
  padding: 0.65rem 0.9rem;
  color: #18181b;
  font: 650 0.78rem/1 ui-sans-serif, system-ui, sans-serif;
  background: #ffffff;
  border: 1px solid #d4d4d8;
  border-radius: 7px;
  cursor: pointer;
}

.motion-demo.motion-demo--text-morph .motion-replay-button:focus-visible,
.motion-demo.motion-demo--text-morph .motion-undo-button:focus-visible,
.motion-demo.motion-demo--text-morph .motion-dismiss-button:focus-visible,
.motion-demo.motion-demo--text-morph .motion-pause-button:focus-visible,
.motion-demo.motion-demo--text-morph [data-reorder-item]:focus-visible,
.motion-demo.motion-demo--text-morph [data-swipe-target]:focus-visible {
  outline: 3px solid rgb(37 99 235 / 32%);
  outline-offset: 3px;
}

.motion-demo.motion-demo--text-morph .motion-status {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.motion-demo.motion-demo--text-morph .motion-ripple-ink {
  position: absolute;
  width: 12px;
  height: 12px;
  pointer-events: none;
  background: rgb(255 255 255 / 72%);
  border-radius: 50%;
  transform: translate(-50%, -50%) scale(0.95);
  opacity: 0;
}

.motion-demo.motion-demo--text-morph .motion-state-stack { position: relative; width: min(100%, 18rem); height: 9rem; }
.motion-demo.motion-demo--text-morph .motion-state { position: absolute; inset: 0; display: grid; place-items: center; padding: 1rem; background: #ffffff; border: 1px solid #e4e4e7; border-radius: 8px; }
.motion-demo.motion-demo--text-morph .motion-state--to { background: #18181b; color: white; }

.motion-demo.motion-demo--text-morph .motion-scroll-track > span { color: #71717a; font: 600 0.72rem/1.2 ui-monospace, monospace; letter-spacing: 0.08em; text-transform: uppercase; }
.motion-demo.motion-demo--text-morph .motion-orbit-item { display: grid; place-items: center; width: 2.5rem; height: 2.5rem; color: white; background: #2563eb; border-radius: 50%; }
.motion-demo.motion-demo--text-morph .motion-text-stack { position: relative; width: min(100%, 18rem); min-height: 4rem; }
.motion-demo.motion-demo--text-morph svg { width: min(100%, 20rem); height: auto; }

.motion-demo.motion-demo--text-morph .motion-word { position: absolute; font-size: 2rem; font-weight: 700; animation: motion-text-morph 240ms cubic-bezier(0.23, 1, 0.32, 1) 120ms both; }
.motion-demo.motion-demo--text-morph .motion-word--from { animation-name: motion-text-morph-out; }
@keyframes motion-text-morph { from { opacity: 0; filter: blur(8px); transform: translateY(0.35em); } to { opacity: 1; filter: blur(0); transform: none; } }
@keyframes motion-text-morph-out { to { opacity: 0; filter: blur(8px); transform: translateY(-0.35em); } }

@keyframes motion-text-morph-reduced-fade {
  from { opacity: 0.84; }
  to { opacity: 1; }
}

.force-reduced-motion .motion-demo.motion-demo--text-morph {
  animation: motion-text-morph-reduced-fade 160ms cubic-bezier(0.23, 1, 0.32, 1) both !important;
}

.force-reduced-motion .motion-demo.motion-demo--text-morph *,
.force-reduced-motion .motion-demo.motion-demo--text-morph *::before,
.force-reduced-motion .motion-demo.motion-demo--text-morph *::after {
  animation: none !important;
  animation-delay: 0ms !important;
  animation-iteration-count: 1 !important;
  transition-property: opacity, color, background-color, border-color !important;
  transition-duration: 160ms !important;
  transition-delay: 0ms !important;
  scroll-behavior: auto !important;
}

.force-reduced-motion .motion-demo.motion-demo--text-morph .motion-button,
.force-reduced-motion .motion-demo.motion-demo--text-morph [data-reorder-item],
.force-reduced-motion .motion-demo.motion-demo--text-morph [data-swipe-target],
.force-reduced-motion .motion-demo.motion-demo--text-morph [data-spring-target] {
  transform: none !important;
}

.force-reduced-motion .motion-demo.motion-demo--text-morph .motion-path { stroke-dashoffset: 0 !important; }
.force-reduced-motion .motion-demo.motion-demo--text-morph .motion-word--from { opacity: 0 !important; visibility: hidden; }
.force-reduced-motion .motion-demo.motion-demo--text-morph .motion-word:not(.motion-word--from) { opacity: 1 !important; filter: none !important; transform: none !important; }

@media (prefers-reduced-motion: reduce) {
  .motion-demo.motion-demo--text-morph {
    animation: motion-text-morph-reduced-fade 160ms cubic-bezier(0.23, 1, 0.32, 1) both !important;
  }

  .motion-demo.motion-demo--text-morph *, .motion-demo.motion-demo--text-morph *::before, .motion-demo.motion-demo--text-morph *::after {
    animation: none !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-property: opacity, color, background-color, border-color !important;
    transition-duration: 160ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }

  .motion-demo.motion-demo--text-morph .motion-button,
  .motion-demo.motion-demo--text-morph [data-reorder-item],
  .motion-demo.motion-demo--text-morph [data-swipe-target],
  .motion-demo.motion-demo--text-morph [data-spring-target] {
    transform: none !important;
  }

  .motion-demo.motion-demo--text-morph .motion-path { stroke-dashoffset: 0 !important; }
  .motion-demo.motion-demo--text-morph .motion-word--from { opacity: 0 !important; visibility: hidden; }
  .motion-demo.motion-demo--text-morph .motion-word:not(.motion-word--from) { opacity: 1 !important; filter: none !important; transform: none !important; }
}
Review notes

Pre-delivery review

  • Test varying lengths, CJK characters, and wrapping boundaries.
  • Ensure screen readers receive only the final value.
Accessibility

Keep information accessible

Reduced motion

Replace the full text immediately and use static highlighting or one short fade to signal change.

Parameters

Parameter reference

ParameterRange / optionsDefaultPurpose
duration80ms–1200ms240msControls the time required for one motion cycle.
blur0px–20px8pxAdjusts transition blur.
delay0ms–600ms120msControls the wait before motion begins.
easeSoft / Snap / Calm / LinearSoftControls how velocity changes over time.