Skip to content

shizukushq/numeric-text

Repository files navigation

NumericText

NPM version bundle size playground

A lightweight, framework-agnostic component for text transitions. As the name suggests, this is a dedicated attempt to replicate the behavior and feel of SwiftUI’s .numericText for the web.

Features

  • Framework-agnostic: Use it with any framework or just Vanilla JS.
  • Zero Dependencies: No third-party runtime dependencies.
  • Ultra-Lightweight: Only 2.38KB (gzipped) for the core package.
  • High Performance: Optimized for 120fps animations.
  • SSR Support: Works with Server-Side Rendering.
  • A11y:
    • Screen Reader Ready: Fully accessible with aria-label and role="img".
    • Universal: Supports all languages and character sets via Intl.Segmenter.
    • RTL Support: Built-in support for Right-to-Left languages like Arabic or Hebrew.
    • Respects Preferences: Automatically disables animations for users with prefers-reduced-motion.

Limitations

  • No multiline support: This is a very specific use case. Adding multiline support would greatly complicate the code and increase the bundle size.
  • No support for ligatures and kerning: Because every character is rendered in its own <span>, typographic features such as ligatures and kerning are not supported.

Installation

# Core (Vanilla JS)
npm install @numeric-text/core

# Framework Wrappers
npm install @numeric-text/svelte
npm install @numeric-text/solid
npm install @numeric-text/react
npm install @numeric-text/vue

Usage

Vanilla JS

<numeric-text></numeric-text>

<script type="module">
  import '@numeric-text/core'
  // Using CDN (without bundler):
  // import 'https://esm.sh/@numeric-text/core'

  const text = document.querySelector('numeric-text')
  // set initial value
  text.value = 'text' // or text.update('text', false)
</script>

Svelte

<script lang="ts">
  import NumericText from '@numeric-text/svelte'
</script>

<NumericText value="text" />

React/Solid.js

import NumericText from '@numeric-text/react'
// or
import NumericText from '@numeric-text/solid'

<NumericText value="text" />

Vue

<script lang="ts" setup>
import NumericText from '@numeric-text/vue'
</script>

<NumericText value="text" />

API Reference

Prop Type Default Description
value string | number "" The text or number to display.
animated boolean true (Wrappers only) Whether to animate changes. For Vanilla JS, use text.update(val, false) for instant updates.
trend 1 | 0 | -1 0 Animation direction (1: up, -1: down, 0: auto-detect based on numbers).
transition Transition { duration: 550, easing: '...' } Custom duration and easing function.
respectMotionPreference boolean true If true, disables animation for users with prefers-reduced-motion.

Why?

Ever since SwiftUI introduced .numericText, I have been waiting for a similar solution to arrive on the web. It is an ideal way to draw a user's attention to interface changes — be it a status update, a price change, or a counter increment.

swiftui-numerictext.mp4

The Limitations of Existing Solutions

Until now, the closest web alternative was number-flow. While it is visually stunning and elegant, it is strictly limited to animating numbers. SwiftUI’s implementation, however, is much more versatile, capable of morphing arbitrary text strings.

Another approach, often referred to as "TextMorph" (popularized by components in motion-primitives), takes a fundamentally different path. To the best of my knowledge, this technique originated in the Family app. It works by assigning unique IDs to every character and animating their individual layout transitions.

text-morph.mp4

However, applying this specific logic to general UI text has significant drawbacks:

  • Visual Chaos: Characters often fly across the screen in a confusing manner. During the transition from "Creative" to "Craft", letters rearrange: the fourth letter "a" moves to replace the "e", the fifth letter "t" flies to the end of "Craft", etc. It lacks a sense of structural cohesion and feels disjointed.
  • Performance Heavy: Animating the layout of every single character is expensive on lower-end mobile devices.

It is quite surprising that this "fly-everywhere" logic was so heavily promoted in @emilkowalski's animations.dev, considering its impact on text legibility and performance during transitions.

The NumericText Approach

Driven by the lack of a proper SwiftUI-like alternative, I built NumericText. It handles both numbers and text, providing a cohesive feel for text transitions, and achieving a visual fluidity similar to number-flow when animating numbers.

Under the hood, NumericText uses a LCP diffing algorithm. Unlike character-level morphing, this ensures the transition feels grounded and predictable. Letters don't fly randomly, they stay anchored where they belong logically.

textmoprh-numerictext-comparing.mp4

This diffing logic is nearly identical to the one used in SwiftUI. By dividing the text into three logical sections — we only need to animate the layout transitions of the sections themselves. This approach delivers better performance, especially on low-end mobile devices.

numerictext-sections.mp4

About

A lightweight web component attempting to replicate SwiftUI’s .numericText

Resources

License

Stars

Watchers

Forks

Contributors