---
title: "TextTransformer"
description: "A short-text transition that morphs per character or crossfades."
category: Effects
status: beta
since: 0.3.4
tags: [text, transition, autosize, live-region]
syncStatus: reviewed
verified: true
---

## Usage

### Basic
When the text color changes with the value, set it on the root; under `fade` the outgoing layer fades out in its old color.
:::TuffDemoWrapper{demo="TextTransformerTextTransformerDemo" code-lang="vue"}
---
code: |
  <template>
    <TxTextTransformer
      mode="fade"
      :text="text"
      :duration-ms="320"
      :blur-px="10"
      :style="{ color: accent ? 'var(--tx-color-primary)' : 'var(--tx-text-color-primary)' }"
    />
  </template>
---
:::

### Morph and Fade
The default `morph` animates per character and rolls digits by place value; `fade` is a whole-string blur crossfade, the only mode `blurPx` applies to.
:::TuffDemoWrapper{demo="TextTransformerMorphVsFadeDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { computed, ref } from 'vue'

  const stages = ['Connecting', 'Connected', 'Syncing 12 files', 'Syncing 148 files', 'Up to date']
  const index = ref(0)
  const stage = computed(() => stages[index.value] ?? '')
  </script>

  <template>
    <!-- Default: per-character morph, digits rolling by place value -->
    <TxTextTransformer :text="stage" :duration-ms="320" />

    <!-- The original whole-string blur crossfade -->
    <TxTextTransformer :text="stage" mode="fade" :duration-ms="320" :blur-px="10" />
  </template>
---
:::

### With AutoSizer
Inside `TxAutoSizer`, wrap the change in `action(() => …)` and the size follows the text; with `wrap=false`, overflow is clipped.
:::TuffDemoWrapper{demo="TextTransformerAutoSizerTextTransformerDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'

  const sizerRef = ref<any>(null)
  const label = ref('Short')

  function toggle() {
    void sizerRef.value?.action?.(() => {
      label.value = label.value === 'Short'
        ? 'Very very long label (blur + fade) that will be clipped while resizing'
        : 'Short'
    })
  }
  </script>

  <template>
    <TxButton @click="toggle">Toggle</TxButton>
    <TxAutoSizer
      ref="sizerRef"
      :width="true"
      :height="true"
      :inline="true"
      :duration-ms="360"
      outer-class="overflow-hidden"
    >
      <TxTextTransformer mode="fade" :text="label" :duration-ms="360" />
    </TxAutoSizer>
  </template>
---
:::

### Long Text / Chapter Switch
`wrap` breaks multi-line text with `pre-line` and forces `fade`.
:::TuffDemoWrapper{demo="TextTransformerLongTextChapterDemo" code-lang="vue"}
---
code: |
  <template>
    <TxAutoSizer ref="sizerRef" :width="true" :height="true" :duration-ms="380" outer-class="overflow-hidden">
      <TxTextTransformer :text="chapter" :duration-ms="380" :blur-px="12" wrap />
    </TxAutoSizer>
  </template>
---
:::

### Title and Subtitle
:::TuffDemoWrapper{demo="TextTransformerTitleSubtitleDemo" code-lang="vue"}
---
code: |
  <template>
    <TxAutoSizer ref="sizerRef" :width="true" :height="true" :inline="true" :duration-ms="320" outer-class="overflow-hidden">
      <TxTextTransformer mode="fade" :text="title" :duration-ms="320" />
      <TxTextTransformer mode="fade" :text="subtitle" :duration-ms="320" />
    </TxAutoSizer>
  </template>
---
:::

### Status Text
:::TuffDemoWrapper{demo="TextTransformerStatusTextDemo" code-lang="vue"}
---
code: |
  <template>
    <TxAutoSizer ref="sizerRef" :width="true" :height="true" :inline="true" :duration-ms="280" outer-class="overflow-hidden">
      <span class="dot" :style="{ background: color }" />
      <TxTextTransformer mode="fade" :text="label" :duration-ms="280" :style="{ color }" />
    </TxAutoSizer>
  </template>
---
:::

### Best Practices

- Use it for labels, titles, badges, and occasional state changes, not for streaming tokens or per-frame counters.
- Use `TxTextMorph` directly when you only need the morph; it adds springs, `numbers`, `locale`, and `cursorIndex`.
- Keep the durations equal when paired with `TxAutoSizer`; `morph` animates its own size, so an outer sizer is usually redundant.
- Keep `wrap=false` in compact buttons and badges; turn `wrap` on only for paragraph or chapter changes.
- Keep slot content light and derived only from the provided `text`: during a `fade`, both layers render it.

## API Reference

### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `text` | `string \| number` | - | The current value, normalized with `String(...)`. |
| `mode` | `'morph' \| 'fade'` | `morph` | `morph` animates per character; `fade` crossfades the whole string with blur. |
| `durationMs` | `number` | `240` | Transition duration in ms; under `fade`, also when the old layer is removed. |
| `blurPx` | `number` | `8` | Blur distance of the crossfade; `fade` only. |
| `tag` | `string` | `span` | Root element tag. |
| `wrap` | `boolean` | `false` | Breaks multi-line text with `pre-line` and forces `fade`. |

### Slots

| Slot | Props | Description |
|------|-------|-------------|
| `default` | `{ text: string }` | Text renderer shared by the current and previous layers; forces `fade`. |

## Overview

- The default slot or `wrap` overrides `mode` and forces `fade`: the engine morphs only plain text and measures on a single line.
- The root is `aria-live="polite"`. Under `morph` the whole value sits in a visually hidden `[tx-morph-sr]` node; under `fade` the old layer is `aria-hidden`, so only the current text is announced.
- `fade` with `wrap=false` truncates to one line with an ellipsis; under `morph` the root is `overflow: visible` so exiting segments aren't clipped, and nothing is truncated.
- Under `fade`, the new layer lands in a transparent, blurred setup state, one style commit is forced, and the transition starts on the next frame; the old layer is removed after `durationMs + 34ms`.
- A newer change cancels the previous timer and animation frame, so a stale transition never removes the current layer.
- Under reduced motion, neither layer transitions; the new text replaces the old at once.

## Technologies

- `morph` renders `TxTextMorph` (see [TextMorph](./text-morph.en.mdc)); `fade` renders two layers whose CSS transitions read `--tx-tt-duration` and `--tx-tt-blur`.
- Source: `packages/tuffex/packages/components/src/text-transformer/`.

<TuffDocSourceLink />
