Spelte is an open source collection of elegant, user friendly components that seamlessly integrate with frameworks and AI models.
<script>
import WordsStagger from '$registry/spelte/words-stagger.svelte';
</script>
<div class="flex items-center justify-center">
<WordsStagger
text="Spelte is an open source collection of elegant, user friendly components that seamlessly integrate with frameworks and AI models."
class="max-w-[600px] text-2xl font-[550] tracking-tight md:text-3xl"
/>
</div>Installation
pnpm dlx shadcn-svelte@latest add https://spelte.dev/r/words-stagger.json<script lang="ts">
import { cn } from '$lib/utils';
import { animate, inView } from 'motion-sv';
interface Props {
text: string;
class?: string;
delay?: number;
stagger?: number;
speed?: number;
autoStart?: boolean;
onStart?: () => void;
onComplete?: () => void;
inViewProp?: boolean;
once?: boolean;
}
let {
text,
class: className,
delay = 0,
stagger = 0.1,
speed = 0.5,
autoStart = true,
onStart,
onComplete,
inViewProp = false,
once = true
}: Props = $props();
const words = $derived(text.split(' ').filter((w) => w.length > 0));
let containerEl = $state<HTMLElement | null>(null);
let wordEls: HTMLElement[] = [];
function registerWord(el: HTMLElement) {
wordEls.push(el);
el.style.opacity = '0';
el.style.transform = 'translateY(10px)';
el.style.filter = 'blur(10px)';
return {
destroy() {
wordEls = wordEls.filter((e) => e !== el);
}
};
}
function runAnimation() {
onStart?.();
wordEls.forEach((el, i) => {
animate(
el,
{ opacity: [0, 1], y: [10, 0], filter: ['blur(10px)', 'blur(0px)'] },
{ type: 'tween', ease: 'easeOut', duration: speed, delay: delay + i * stagger }
);
});
if (wordEls.length > 0 && onComplete) {
const lastDelay = delay + (wordEls.length - 1) * stagger + speed;
setTimeout(onComplete, lastDelay * 1000);
}
}
$effect(() => {
if (!containerEl) return;
if (inViewProp) {
const cleanup = inView(containerEl, () => {
runAnimation();
if (once) return () => {};
});
return cleanup as () => void;
} else if (autoStart) {
runAnimation();
}
});
</script>
<div bind:this={containerEl} class={cn('flex flex-wrap', className)}>
{#each words as word, index}
<span class="inline-block" use:registerWord>
{word}{#if index < words.length - 1}<span class="inline-block"> </span>{/if}
</span>
{/each}
</div>
Examples
Speed
Ship thoughtful interfaces quickly.
<script lang="ts">
import WordsStagger from '$registry/spelte/words-stagger.svelte';
<\/script>
<WordsStagger
text="Ship thoughtful interfaces quickly."
delay={0.2}
class="text-3xl font-[550] tracking-tight"
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
text | string | — | Text content to animate |
delay | number | 0 | Delay before first word animates (seconds) |
stagger | number | 0.1 | Delay between each word (seconds) |
speed | number | 0.5 | Duration of each word's animation (seconds) |
autoStart | boolean | true | Auto-start on mount |
inViewProp | boolean | false | Trigger only when in viewport |
once | boolean | true | Only animate once |
onStart | () => void | — | Callback when animation starts |
onComplete | () => void | — | Callback when animation completes |
class | string | — | Additional CSS classes |