Skip to content
Jace edited this page Aug 30, 2026 · 1 revision

API

<scritto-text>

<scritto-text id="count">0</scritto-text>
const el = document.querySelector('#count')
el.update('1')            // rolls
el.value = '2'            // sets it without rolling
el.setOptions({ trend: 1, transition: { duration: 300 } })

update() rolls and the value setter does not. They are one entry point: set value(v) is update(v, false). Call update(next) when you want motion, and assign value when you want the new text to appear without it. update(next, false) is the explicit form of the same call.

A value with a space in it wraps like the text around it. It breaks between its words and never inside one, so a sentence in a narrow column fills each line rather than taking one of its own or overhanging the card. A value with no spaces, a long formatted number for instance, has nowhere to break and runs past a narrow container as it always has. The box animates its width only while the value is on a single line: wrapped, the lines it fills are the layout and the roll rides them as they are.

Text content inside the tag is the server-rendered value. Import @scritto/core/ssr.css if the element has to look right before its script runs. Without it the markup is unstyled until the definition upgrades.

Options

option default what it does
transition { duration: 550, easing: <spring> } one glyph's animation
trend 0 1 up, -1 down, 0 reads it off the value
bounce false swaps the spring for the bouncier preset
respectMotionPreference true honour prefers-reduced-motion
edgeFade 'auto' 'auto', 'always' or 'never': whether a travelling edge fades its ghosts

transition.duration is one glyph's animation, not the whole change. The last glyph starts late, so a 400ms duration finishes somewhere past 460ms depending on how much of the value changed. Timing has the arithmetic and how to read the true end off the animations.

edgeFade: 'auto' fades a ghost only where it has somewhere to escape to. A value with room around it is left alone, so its ghosts dissolve on their own opacity and keep their shape. The fade arms when they would reach a neighbour on the line or leave the box a reader sees as holding the value, which is the case a card, a pill or a tight container creates. Force it either way with 'always' or 'never'.

trend: 0 compares every number in the two values and lets the first one that differs decide. Values that gain or lose a number read as a rise.

Events

scrittochange fires twice per update, detail.phase being before then after, with detail.animate saying whether this one is rolling. It bubbles, which is how <scritto-flow> hears about its own hosts, the <scritto-text> elements inside it.

Setting a value equal to the current one fires nothing at all. update() returns early when the value has not changed, so neither phase is dispatched. Await the after event to sequence something and it never resolves. Compare against the current value first if the new one might be identical.

When nothing happens

The element never throws. Every failure is a quiet no-op instead, so the table below is every case where a call produces no motion. All of it is verified by bun run check:docs.

You do this What happens Why
update(v) where v equals the current value Nothing at all. No roll, no event. update() returns before it dispatches
el.value = v The text changes with no animation, both events fire with detail.animate === false The setter is update(v, false)
Anything, with prefers-reduced-motion: reduce The value updates instantly, both events fire with detail.animate === false respectMotionPreference is true by default
Anything, while the element is scrolled out of view The value updates instantly, no animations are created An element out of view does not animate
A change whose width moves less than half a pixel The glyphs still roll, the box does not animate A sub-pixel width change is not visible

Plan for the first row. Awaiting the after event to sequence work after a roll hangs forever if the value happened to be unchanged. Compare against el.value first, or see Recipes for a helper that waits on the animations instead.

<scritto-flow>

Wraps a line so the words beside a value move with it. No options; see Flow.

It captures its word list once, at connect. Replacing a word's element afterwards, by writing textContent over a container for instance, leaves the flow animating a node that is no longer on the page. Write into the word, not over it.

Framework packages

@scritto/react, @scritto/vue, @scritto/svelte, @scritto/solid. Each is a thin wrapper over the element, 0.49–0.67 KB gzipped measured by bun run size, taking the same options as props plus value and animated.

<Scritto value={count} trend={1} transition={{ duration: 300 }} />

They bring @scritto/core with them; installing both is only necessary if you also use the element directly.

Clone this wiki locally