Rotating words
One word exits up, the next rises from below. A clipped column on one variable.
we build brandswebsitesproducts
<h1 class="text-5xl font-extrabold">
we build
<span class="sv-words text-violet-400" aria-hidden="true">
<span>brands</span><span>websites</span><span>products</span>
</span>
<span class="sr-only">brands, websites and products</span>
</h1>
<button type="button" id="words-pause" aria-pressed="false">Pause rotation</button>
<!-- set --sv-word from your state; scenes drive it for free in pinned stories -->
<script>
let paused = false
document.getElementById('words-pause').addEventListener('click', () => {
paused = !paused
document.getElementById('words-pause').setAttribute('aria-pressed', String(paused))
})
// guard your own --sv-word write the same way: if (!paused) el.style.setProperty(...)
</script>
<h1>we build
<span class="sv-words" aria-hidden="true">
<span>brands</span><span>websites</span><span>products</span>
</span>
<!-- the column reads as three words in a row to a screen reader: hide it and say the phrase once -->
<span style="position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap">brands, websites and products</span>
</h1>
<!-- WCAG 2.2.2: text that auto-rotates past 5s needs a pause control -->
<button type="button" id="words-pause" aria-pressed="false">Pause rotation</button>
/* the preset (styles/state.css): */
.sv-words { display: inline-flex; flex-direction: column;
vertical-align: baseline; height: 1.15em; overflow: hidden; }
.sv-words > * { display: block; height: 1.15em; line-height: 1.15;
translate: 0 calc(var(--sv-word, 0) * -1.15em);
transition: translate .65s var(--sv-ease, cubic-bezier(.28,.84,.42,1)); }
/* the same sheet's reduced-motion override, last so it wins on source order: */
@media (prefers-reduced-motion: reduce) {
.sv-words > * { transition-duration: .01ms; }
}
/* the same under html[data-sv-motion="reduce"], the site's own switch */
:where([data-sv-motion="reduce"]) .sv-words > * { transition-duration: .01ms; }
// drive it (state, scenes, or a timer), and stop on request:
let paused = false
document.getElementById('words-pause').addEventListener('click', () => {
paused = !paused
document.getElementById('words-pause').setAttribute('aria-pressed', String(paused))
})
if (!paused) el.style.setProperty('--sv-word', nextIndex)
const [word, setWord] = useState(0)
<h1>we build{' '}
<span className="sv-words" style={{ '--sv-word': word } as React.CSSProperties} aria-hidden="true">
{words.map(w => <span key={w}>{w}</span>)}
</span>
{/* the column reads as three words in a row to a screen reader: say the phrase once */}
<span className="sr-only">{words.join(', ')}</span>
</h1>