Composables

useSplitText

Split text into lines, words and characters with AnimeJS splitText().

useSplitText wraps the splitText function from AnimeJS. It breaks text into individual lines, words, or characters wrapped in HTML span elements so they can be animated independently.

When target elements or parameters change, nanime cleans up split elements and re-splits the text while preserving registered effects across rebuilds.

Usage

Hello World!
import { stagger } from '#nanime/utils'

const text = useTemplateRef('phrase')
const { chars } = useSplitText(text, {
  chars: true,
})

useWaapiAnimate(chars, {
  y: ['-30%', 0],
  delay: stagger(50),
  duration: 1000,
  ease: 'outBounce',
  alternate: true,
  loop: true,
})
View on GitHub

Signature

function useSplitText(
  target: MaybeRef<SplitTextTargets>,
  parameters?: MaybeRefOrGetter<TextSplitterParams>,
  options?: SplitTextOptions
): SplitText

Parameters

target
MaybeRef<SplitTextTargets> required
Text container element. Accepts CSS selectors, DOM elements, and template refs.
parameters
MaybeRefOrGetter<TextSplitterParams>
Accepts a plain object such as { chars: true }, a ref or a getter. The text is split again when a value in it changes. A new object with the same values keeps the current split, but an inline function such as chars: (el) => ... counts as a change on every render. See the AnimeJS splitText documentation for the options.
options
SplitTextOptions
html takes MaybeRefOrGetter<string | null | undefined> and defaults to the target's own content. It is the text to split. When it changes, the new text is split in place. While it is null or undefined, the current text stays and a warning is logged in development. Render the element with v-once when you use it.

Returns

Returns a plain object of refs and computed getters. Destructuring is safe.

MemberTypeNotes
linesRef<HTMLElement[]>The line elements, empty until mount.
wordsRef<HTMLElement[]>The word elements, empty until mount.
charsRef<HTMLElement[]>The character elements, empty until mount.
propertiesComputedRefNon-function properties of the underlying splitter instance, except lines, words and chars. undefined before mount.
methodsComputedRef<SplitterMethods>Splitter methods (revert, split, refresh), with addEffect wrapped to persist. undefined before mount.
refresh()() => voidSplits the text again after you change it by hand.

Caveats

Arrays are empty during setup

// wrong: chars.value is still [] during setup
const heading = useTemplateRef('heading')
const { chars } = useSplitText(heading, { chars: true })
console.log(chars.value.length) // 0

// right: pass the ref directly to an animation composable
useAnimate(chars, { opacity: [0, 1], delay: stagger(40) })

DOM elements do not exist until the component mounts, so lines, words, and chars stay empty during setup. Passing .value immediately reads an empty array and attaches zero animation targets. Pass the refs directly to animation composables or inspect them inside a watcher after mount.

Render changing text with v-once

<script setup lang="ts">
const heading = useTemplateRef('heading')
const name = ref('Ada')
const html = computed(() => `Hello <strong>${name.value}</strong>`)
const { chars } = useSplitText(heading, { chars: true }, { html })
</script>

<template>
  <!-- wrong: Vue patches text that the split already replaced -->
  <h1 ref="heading">Hello <strong>{{ name }}</strong></h1>

  <!-- right: Vue renders the text once, the html option handles changes -->
  <h1 ref="heading" v-once v-html="html" />
</template>

AnimeJS keeps the text of a split in its html property and puts it back on every split. Text that Vue re-renders inside the element is lost on the next split. With v-once, Vue renders the first text, on the server too, and never touches the element again. The html option then sets that property and splits again when it changes, as the AnimeJS refresh() docs describe.

Manual content updates require refresh

// wrong: innerHTML changes are not detected automatically
heading.value.textContent = 'Updated message'

// right: call refresh to re-split updated text
heading.value.textContent = 'Updated message'
refresh()

nanime does not attach DOM mutation observers to split elements. When you manually mutate text content or update elements without changing the composable parameters, the split structure remains stale. Call refresh() after modifying text content to force an immediate re-split of the new text.

Type reference

See also

Copyright © 2026