npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@wix/splittext

v0.1.2

Published

Lightweight, accessible text splitting utility for creating staggered animations on characters, words, and lines.

Readme

@wix/splittext

Lightweight, accessible text splitting utility for creating staggered animations on characters, words, lines, and sentences.

npm version license

Features

  • Split by chars, words, lines, or sentences — each piece wrapped in an animatable <span>
  • Locale-aware segmentation — uses Intl.Segmenter (handles emoji, unicode, grapheme clusters)
  • Range API line detection — accurate line breaks from browser rendering, no pre-wrapping required
  • Lazy evaluation — DOM is only mutated when a result getter is first accessed
  • Accessible by default — original text preserved for screen readers and SEO
  • Customizable wrappers — add classes, inline styles, and data attributes per split type
  • CSS stagger hooks--char-index, --word-index, etc. set automatically on every span
  • React hookuseSplitText with automatic cleanup on unmount
  • Responsive — optional autoSplit re-splits on resize and font load
  • Revertible — restore the original DOM at any time with result.revert()

Installation

npm install @wix/splittext

Browser requirement: Intl.Segmenter (Chrome 87+, Safari 14.1+, Firefox 125+). For older environments supply a polyfill via the segmenter option.

Quick Start

import { splitText } from '@wix/splittext';

// Lazy — DOM unchanged until getter accessed
const result = splitText('.headline');
const chars = result.chars; // DOM split into chars here, cached

// Eager — split immediately on call
const { chars } = splitText('.headline', { type: 'chars' });

// Animate with the Web Animations API
chars.forEach((char, index) => {
  char.animate(
    { opacity: [0, 1], transform: ['translateY(8px)', 'translateY(0)'] },
    { duration: 400, delay: index * 30, fill: 'forwards' },
  );
});

When type is an array of two or more values, a nested DOM tree is built (coarse → fine), e.g. words containing chars. Array order is normalized automatically (lines → sentences → words → chars). Multi-type splits require nested: 'flatten'.

// Words containing chars — order in the array does not matter
splitText('.headline', {
  nested: 'flatten',
  type: ['chars', 'words'],
});

Staggered CSS animation

splitText('.headline', {
  type: 'chars',
  wrapperClass: 'char',
});
.char {
  opacity: 0;
  animation: fadeUp 0.4s ease forwards;
  animation-delay: calc(var(--char-index) * 0.04s);
}
@keyframes fadeUp {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

React

useSplitText returns null on the first render and during SSR because the ref is not attached yet. After mount it returns a SplitTextResult; the element is automatically reverted on unmount. Memoize function-valued options (bidiResolver, onSplit, segmenter, ignore) with useCallback/useMemo to avoid re-splitting on every render.

import { useRef, useEffect } from 'react';
import { useSplitText } from '@wix/splittext/react';

function Headline() {
  const ref = useRef<HTMLHeadingElement>(null);
  const result = useSplitText(ref, { type: 'chars' });

  useEffect(() => {
    if (!result) return;
    // animate result.chars …
  }, [result]);

  return <h1 ref={ref}>Hello World</h1>;
}

API

splitText(target, options?)

| Parameter | Type | Description | | --------- | ----------------------- | ------------------------- | | target | string \| HTMLElement | CSS selector or element | | options | SplitTextOptions | Configuration (see below) |

Returns a SplitTextResult with lazy getters: .chars, .words, .lines, .sentences.

Options

| Option | Type | Default | Description | | ------------------ | ------------------------------------------------------------------------------------ | ------------ | ------------------------------------------------------------------------- | | type | 'chars' \| 'words' \| 'lines' \| 'sentences' or array of those | — | Split eagerly on call instead of lazily | | wrapperClass | string or { chars?: string, words?: string, lines?: string, sentences?: string } | — | Extra CSS class(es) on wrapper spans | | wrapperStyle | CSS style object or { chars?, words?, lines?, sentences? } per-type partial styles | — | Inline styles on wrapper spans | | wrapperAttrs | Record<string, string> or { chars?, words?, lines?, sentences? } per-type attrs | — | Custom attributes on wrapper spans | | contentAttribute | 'none' \| 'both' \| 'attribute-only' | 'both' | both: text content and data-content; attribute-only: attribute only | | aria | 'auto' \| 'none' | 'auto' | ARIA handling mode | | preserveText | boolean | true | Insert visually-hidden original text for a11y/SEO | | partIndexing | boolean | true | Set --char-index / --word-index etc. on spans | | wordGlue | 'adjacent' \| 'none' | 'adjacent' | Glue punctuation to words, or wrap it separately | | nested | 'flatten' \| 'preserve' \| number | 'preserve' | How inner DOM structure is handled | | autoSplit | boolean | false | Re-split on resize / font load | | onSplit | (result) => void | — | Callback after each split | | segmenter | Intl.Segmenter or constructor | — | Polyfill for Intl.Segmenter | | ignore | CSS selector string or (node) => boolean | — | Skip matching elements during traversal (selector uses native OR via ,) |

Default CSS classes

Every wrapper <span> automatically receives a built-in class in addition to any wrapperClass you pass:

| Split type | Class added automatically | | ----------- | ------------------------- | | chars | .split-c | | words | .split-w | | lines | .split-l | | sentences | .split-s |

Base styles (display: inline-block, etc.) are injected once via adoptedStyleSheets.

Accessibility

With defaults (aria: 'auto', preserveText: true), split content is wrapped in an aria-hidden inner div and a screen-reader-accessible copy of the original text is injected as a sibling:

<h1>
  <span class="sr-only" data-splittext-sr>Original text</span>
  <div aria-hidden="true" data-splittext-wrapper>
    <span class="split-c" data-content="H">H</span>
    <!-- … -->
  </div>
</h1>

When preserveText is false, aria-label is set on the container instead of injecting the visually-hidden span. With aria: 'none', no ARIA attributes are added — a transparent wrapper div is still used so callers have a consistent element to append split spans to.

Screen readers and crawlers see the original text; the split spans are hidden from the accessibility tree. result.revert() restores originalHTML captured at construction time. On re-split (autoSplit), plain text is re-read from the element so content changes are picked up.

License

MIT