Text

Labels that change in place, with letters sliding to their new positions, and numbers whose digits roll, like SwiftUI's content transitions. This is torph, not a Fluid Kit package; this page shows how it works with the kit.

On this page

Get started

There’s already a great library for animated text: torph by Lochie Axon. It has no dependencies and comes with React, Vue and Svelte components. It animates text letter by letter (shared letters slide to their new positions) and numbers digit by digit. Fluid Kit doesn’t wrap it: use torph directly, and the kit’s components follow the size changes it causes.

  1. Install.

    pnpm add torph
  2. Animate an element’s text. Pass the element, then the new text.

    import { TextMorph } from "torph";
    
    const label = new TextMorph({
      element: document.querySelector(".label")!,
      // Use a spring, like the rest of the kit
      ease: { stiffness: 260, damping: 30 },
    });
    
    label.update("Following");
  1. Install.

    pnpm add torph
  2. Animate a label. torph has its own React component: its children are the text.

    import { TextMorph } from "torph/react";
    
    export function FollowButton() {
      const [on, setOn] = useState(false);
      return (
        <button className="lb lb-interactive" onClick={() => setOn(!on)}>
          <TextMorph ease={{ stiffness: 260, damping: 30 }}>{on ? "Following" : "Follow"}</TextMorph>
        </button>
      );
    }
  1. Install.

    pnpm add torph
  2. Animate a label. torph has its own Vue component: text is the text.

    <script setup lang="ts">
    import { ref } from "vue";
    import { TextMorph } from "torph/vue";
    
    const on = ref(false);
    </script>
    
    <template>
      <button class="lb lb-interactive" @click="on = !on">
        <TextMorph :text="on ? 'Following' : 'Follow'" :ease="{ stiffness: 260, damping: 30 }" />
      </button>
    </template>
  1. Install.

    pnpm add torph
  2. Animate a label. torph has its own Svelte component: text is the text.

    <script lang="ts">
      import { TextMorph } from "torph/svelte";
    
      let on = $state(false);
    </script>
    
    <button class="lb lb-interactive" onclick={() => (on = !on)}>
      <TextMorph text={on ? "Following" : "Follow"} ease={{ stiffness: 260, damping: 30 }} />
    </button>

For prices, percentages and counters where digits spin through intermediate values, try NumberFlow. It’s for numbers only, formatted with Intl.

Examples

On glass

When the label changes, torph animates its width along with the letters. The glass around it is CSS, so it follows automatically, including press effects. Try Follow, or Get the app.

Tabs with counts

Tabs sized to their labels, each with a count. When new mail arrives, the count rolls, the tab grows, and the Indicator thumb on it follows exactly, frame by frame.

A total

A quantity and total in a glass card. Digits roll up when the number grows and down when it shrinks. Only the digits that change move; $ and . stay put.

Glass Mug$12.50 each
1
Total$12.50

With the kit

Glass

Glass is CSS on the element, so when a label grows, the glass grows in the same frame, highlight and corners included. A glass group redraws in the same frame its child resizes.

Indicator

An Indicator resting on an item that resizes follows it exactly. When moving to it, it lands on the item’s final size.

Morph

If a Morph state resizes during the animation (a label changing as a panel opens), the shape lands on its final size.

Notes

  • torph changes the element’s children while animating, so give it its own element, like a <span> inside the button, not the button itself.
  • It respects reduced motion by default: the text changes instantly.
  • For an editable value, pass the caret position to update() so a typed digit is inserted instead of all digits shifting. See torph’s README.
PlaygroundIts hard cases, live: slowed down, on other backdrops, with their boxes drawn.