- Docs
- Framework integrations
Framework integrations
Use Ticker in React, Vue, Svelte, Astro, Shopify and WordPress with accessible playback controls and explicit client lifecycle ownership.
Ticker uses the same browser API in each host; there is no framework adapter or peer dependency to install. Package / ESM imports ticker() and its stylesheet. HTML / CDN calls window.Ticker.ticker() after loading the standalone script and stylesheet. Keep delivery separate from ownership; see loading terms.
JavaScript and TypeScript
Start with installation. JavaScript uses the same calls without type annotations. TypeScript can import TickerOptions and TickerInstance from the root; use satisfies TickerOptions to check reusable settings. The TypeScript reference lists every exported type.
React, Vue and Svelte
Install Ticker in an existing framework project, save the component and its CSS sibling, then render NewsTicker. These examples include a Pause/Play action that follows requested playback, including reduced-motion or other automatic pauses. They create after client mount and destroy before the component leaves. React’s example also supports its development setup/cleanup cycle.
React
React
'use client'
import { useEffect, useRef, useState } from 'react'
import { ticker } from '@samline/ticker'
import '@samline/ticker/style.css'
import './ticker.css'
export const NewsTicker = () => {
const source = useRef(null)
const controller = useRef(null)
const [playing, setPlaying] = useState(true)
useEffect(() => {
const news = ticker(source.current, { duration: 20 })
if (!news) return
controller.current = news
const stop = news.on('playback', state => setPlaying(state.requested))
setPlaying(news.state.requested)
return () => {
stop()
news.destroy()
controller.current = null
}
}, [])
const toggle = () => {
const news = controller.current
if (news?.state.requested) news.pause()
else news?.play()
}
return (
<section aria-label="Latest news">
<div ref={source}>
<span className="ticker-message">Made for the open web</span>
<span className="ticker-message">Your content, in motion</span>
<span className="ticker-message">Something good is coming</span>
</div>
<button type="button" onClick={toggle}>
{playing ? 'Pause' : 'Play'}
</button>
</section>
)
}
CSS
.ticker-message {
display: inline-block;
padding: 0.75rem 1.25rem;
}
Vue
Vue
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { ticker } from '@samline/ticker'
import '@samline/ticker/style.css'
import './ticker.css'
const source = ref(null)
const playing = ref(true)
let news
let stop
onMounted(() => {
news = ticker(source.value, { duration: 20 })
if (!news) return
playing.value = news.state.requested
stop = news.on('playback', state => {
playing.value = state.requested
})
})
onUnmounted(() => {
stop?.()
news?.destroy()
})
const toggle = () => {
if (news?.state.requested) news.pause()
else news?.play()
}
</script>
<template>
<section aria-label="Latest news">
<div ref="source">
<span class="ticker-message">Made for the open web</span>
<span class="ticker-message">Your content, in motion</span>
<span class="ticker-message">Something good is coming</span>
</div>
<button type="button" @click="toggle">{{ playing ? 'Pause' : 'Play' }}</button>
</section>
</template>
CSS
.ticker-message {
display: inline-block;
padding: 0.75rem 1.25rem;
}
Svelte
Svelte
<script>
import { onMount } from 'svelte'
import { ticker } from '@samline/ticker'
import '@samline/ticker/style.css'
import './ticker.css'
let source
let news
let playing = $state(true)
onMount(() => {
news = ticker(source, { duration: 20 })
if (!news) return
playing = news.state.requested
const stop = news.on('playback', state => { playing = state.requested })
return () => { stop(); news.destroy() }
})
const toggle = () => { if (news?.state.requested) news.pause(); else news?.play() }
</script>
<section aria-label="Latest news">
<div bind:this={source}>
<span class="ticker-message">Made for the open web</span>
<span class="ticker-message">Your content, in motion</span>
<span class="ticker-message">Something good is coming</span>
</div>
<button type="button" onclick={toggle}>{playing ? 'Pause' : 'Play'}</button>
</section>
CSS
.ticker-message {
display: inline-block;
padding: 0.75rem 1.25rem;
}
The enhanced source DOM belongs to Ticker until destroy() restores it. Keep this subtree stable while mounted: use a component ref for the controller, update() for settings, and refresh() after application-owned source edits. Do not replace the runtime’s track/clones from a framework render. For reactive item lists, tear down before the framework replaces the original nodes, then initialize the new source. Do not place framework components in generated clones.
The lifecycle hooks follow React Effects, Vue mount/unmount hooks and Svelte onMount cleanup.
Astro
Import and call ticker() inside a processed client <script>, not frontmatter. The first integration is ordinary browser JavaScript and needs no hydrated framework component. With Astro client navigation, initialize the current page after astro:page-load and destroy the owning instances on astro:before-swap; keep document listeners registered once. Full-page examples preserve instances on persisted pagehide and clean up on a real departure.
Shopify
Use a theme section and theme assets. Copy the published 2.1.0 dist/browser/global.global.js as assets/ticker.global.js and dist/style.css as assets/ticker.css; save the JavaScript and CSS below beside them. Add the loader once in layout/theme.liquid, then add sections/ticker-strip.liquid in the theme editor. The section itself contains no extra runtime script tags.
Section
<section data-ticker-widget aria-label="Latest news">
<div data-source>
<span class="ticker-message">Made for the open web</span>
<span class="ticker-message">Your content, in motion</span>
<span class="ticker-message">Something good is coming</span>
</div>
<button type="button" data-toggle disabled>Pause</button>
</section>
{% schema %}
{"name":"Ticker example","presets":[{"name":"Ticker example"}]}
{% endschema %}
Layout
{{ 'ticker.css' | asset_url | stylesheet_tag }}
{{ 'integration.css' | asset_url | stylesheet_tag }}
<script src="{{ 'ticker.global.js' | asset_url }}" defer></script>
<script src="{{ 'integration.js' | asset_url }}" defer></script>JavaScript
const instances = new Map()
const mount = scope => {
scope.querySelectorAll('[data-ticker-widget]').forEach(root => {
if (instances.has(root)) return
const button = root.querySelector('[data-toggle]')
const news = window.Ticker.ticker(root.querySelector('[data-source]'), { duration: 20 })
if (!news) return
const events = new AbortController()
const render = () => {
button.textContent = news.state.requested ? 'Pause' : 'Play'
}
const stop = news.on('playback', render)
render()
button.disabled = false
button.addEventListener(
'click',
() => {
if (news.state.requested) news.pause()
else news.play()
},
{ signal: events.signal }
)
instances.set(root, () => {
events.abort()
stop()
news.destroy()
button.disabled = true
})
})
}
const unmount = scope => {
for (const [root, dispose] of instances) {
if (scope.contains(root)) {
dispose()
instances.delete(root)
}
}
}
mount(document)
document.addEventListener('shopify:section:load', event => mount(event.target))
document.addEventListener('shopify:section:unload', event => unmount(event.target))
window.addEventListener('pagehide', event => {
if (!event.persisted) unmount(document)
})
CSS
.ticker-message {
display: inline-block;
padding: 0.75rem 1.25rem;
}
The loader creates independent instances for repeated sections, handles shopify:section:load, and destroys only the removed section on shopify:section:unload. Static content remains readable without JavaScript. Shopify’s section lifecycle and asset loading describe this host contract.
WordPress
Create wp-content/plugins/ticker-example/ with the plugin PHP file below and an assets/ folder. Save the same pinned runtime files there as ticker.global.js and ticker.css, plus the provided integration.js and integration.css. Activate Ticker example, then put [ticker_strip] in a Shortcode block on a normal post or page.
Plugin
<?php
/**
* Plugin Name: Ticker example
*/
add_action('wp_enqueue_scripts', static function () {
$post = get_post();
if (!is_singular() || !$post || !has_shortcode($post->post_content, 'ticker_strip')) return;
$assets = plugin_dir_url(__FILE__) . 'assets/';
wp_enqueue_style('ticker-runtime', $assets . 'ticker.css', [], '2.1.0');
wp_enqueue_style('ticker-example', $assets . 'integration.css', ['ticker-runtime'], '1.0.0');
wp_enqueue_script('ticker-runtime', $assets . 'ticker.global.js', [], '2.1.0', ['strategy' => 'defer', 'in_footer' => true]);
wp_enqueue_script('ticker-example', $assets . 'integration.js', ['ticker-runtime'], '1.0.0', ['strategy' => 'defer', 'in_footer' => true]);
});
add_shortcode('ticker_strip', static fn () => '<section data-ticker-widget aria-label="Latest news">
<div data-source>
<span class="ticker-message">Made for the open web</span>
<span class="ticker-message">Your content, in motion</span>
<span class="ticker-message">Something good is coming</span>
</div>
<button type="button" data-toggle disabled>Pause</button>
</section>');
JavaScript
const instances = new Map()
const mount = scope => {
scope.querySelectorAll('[data-ticker-widget]').forEach(root => {
if (instances.has(root)) return
const button = root.querySelector('[data-toggle]')
const news = window.Ticker.ticker(root.querySelector('[data-source]'), { duration: 20 })
if (!news) return
const events = new AbortController()
const render = () => {
button.textContent = news.state.requested ? 'Pause' : 'Play'
}
const stop = news.on('playback', render)
render()
button.disabled = false
button.addEventListener(
'click',
() => {
if (news.state.requested) news.pause()
else news.play()
},
{ signal: events.signal }
)
instances.set(root, () => {
events.abort()
stop()
news.destroy()
button.disabled = true
})
})
}
const unmount = scope => {
for (const [root, dispose] of instances) {
if (scope.contains(root)) {
dispose()
instances.delete(root)
}
}
}
mount(document)
window.addEventListener('pagehide', event => {
if (!event.persisted) unmount(document)
})
CSS
.ticker-message {
display: inline-block;
padding: 0.75rem 1.25rem;
}
This example targets WordPress 6.3+ deferred-script arguments. Runtime and integration scripts are queued with explicit dependencies, so markup is present before initialization. The sample loads assets for ordinary singular content containing the shortcode; widgets, page builders and AJAX navigation need their own enqueue/mount policy. Do not paste a second IIFE into each shortcode. See wp_enqueue_script and frontend asset hooks.
Ownership and accessibility
Keep original content meaningful and provide a pause action for moving content. Ticker owns clones, measurements, reduced-motion handling and automatic pause causes. Each host owns its original DOM, labels, subscriptions and teardown. Closing a page widget is not permission to destroy unrelated registry entries; see lifecycle ownership and accessibility.