<script>
import Marquee from '$registry/spelte/marquee.svelte';
const logoItems = [
{ src: '/logos/vercel.svg', alt: 'Vercel logo' },
{ src: '/logos/google.svg', alt: 'Google logo' },
{ src: '/logos/framer.svg', alt: 'Framer logo' },
{ src: '/logos/discord.svg', alt: 'Discord logo' },
{ src: '/logos/openai.svg', alt: 'OpenAI logo' },
{ src: '/logos/phantom.svg', alt: 'Phantom logo' },
{ src: '/logos/descript.svg', alt: 'Descript logo' },
{ src: '/logos/netflix.svg', alt: 'Netflix logo' },
{ src: '/logos/linear.svg', alt: 'Linear logo' },
{ src: '/logos/notion.svg', alt: 'Notion logo' },
{ src: '/logos/shopify.svg', alt: 'Shopify logo' },
{ src: '/logos/duolingo.svg', alt: 'Duolingo logo' },
{ src: '/logos/ramp.svg', alt: 'Ramp logo' },
{ src: '/logos/tesla.svg', alt: 'Tesla logo' },
{ src: '/logos/opensea.svg', alt: 'OpenSea logo' },
{ src: '/logos/cursor.svg', alt: 'Cursor logo' }
];
</script>
<Marquee class="flex py-4" duration={40}>
{#each logoItems as logo}
<img
src={logo.src}
alt={logo.alt}
width="96"
height="96"
class="mx-8 h-24 w-24 select-none object-contain opacity-70 not-dark:invert-100 pointer-events-none"
/>
{/each}
</Marquee>Installation
pnpm dlx shadcn-svelte@latest add https://spelte.dev/r/marquee.json<script lang="ts">
import { cn } from '$lib/utils';
import type { Snippet } from 'svelte';
interface Props {
children: Snippet;
class?: string;
duration?: number;
pauseOnHover?: boolean;
direction?: 'left' | 'right' | 'up' | 'down';
fade?: boolean;
fadeAmount?: number;
[key: string]: unknown;
}
let {
children,
class: className,
duration = 20,
pauseOnHover = false,
direction = 'left',
fade = true,
fadeAmount = 10,
...rest
}: Props = $props();
let isPaused = $state(false);
const isVertical = $derived(direction === 'up' || direction === 'down');
const animationName = $derived(
isVertical
? (direction === 'up' ? 'marquee-scroll-y' : 'marquee-scroll-y-reverse')
: (direction === 'left' ? 'marquee-scroll' : 'marquee-scroll-reverse')
);
const maskImage = $derived(
fade
? (isVertical
? `linear-gradient(to bottom, transparent 0%, black ${fadeAmount}%, black ${100 - fadeAmount}%, transparent 100%)`
: `linear-gradient(to right, transparent 0%, black ${fadeAmount}%, black ${100 - fadeAmount}%, transparent 100%)`)
: undefined
);
</script>
<div
{...rest}
class={cn('flex w-full overflow-hidden', isVertical && 'flex-col', className)}
style={maskImage ? `-webkit-mask-image: ${maskImage}; mask-image: ${maskImage};` : undefined}
onmouseenter={() => pauseOnHover && (isPaused = true)}
onmouseleave={() => pauseOnHover && (isPaused = false)}
>
<div
class={cn('marquee-scroller flex shrink-0', isVertical && 'flex-col', isPaused && 'paused')}
style="animation: {animationName} {duration}s linear infinite; animation-play-state: {isPaused ? 'paused' : 'running'};"
>
<div class={cn('flex shrink-0', isVertical && 'w-full')}>
{@render children()}
</div>
<div class={cn('flex shrink-0', isVertical && 'w-full')}>
{@render children()}
</div>
</div>
</div>
Examples
Pause on Hover
<script lang="ts">
import Marquee from '$registry/spelte/marquee.svelte';
const logos = [
{ src: '/logos/vercel.svg', alt: 'Vercel logo' },
{ src: '/logos/google.svg', alt: 'Google logo' },
{ src: '/logos/framer.svg', alt: 'Framer logo' },
{ src: '/logos/discord.svg', alt: 'Discord logo' }
];
<\/script>
<Marquee class="flex py-4" pauseOnHover>
{#each logos as logo (logo.src)}
<img
src={logo.src}
alt={logo.alt}
class="mx-8 h-16 w-16 object-contain opacity-70 not-dark:invert-100"
/>
{/each}
</Marquee>Props
| Prop | Type | Default | Description |
|---|---|---|---|
duration | number | 20 | Animation duration in seconds |
pauseOnHover | boolean | false | Pause on hover |
direction | "left" | "right" | "up" | "down" | "left" | Scroll direction |
fade | boolean | true | Apply fade gradient at edges |
fadeAmount | number | 10 | Fade gradient percentage |
class | string | — | Additional CSS classes |