Infinite Moving Cards

A customizable group of cards that move infinitely in a loop. Made with Framer Motion and Tailwind CSS.

  • It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair.
    Charles Dickens A Tale of Two Cities
  • To be, or not to be, that is the question: Whether 'tis nobler in the mind to suffer The slings and arrows of outrageous fortune, Or to take Arms against a Sea of troubles, And by opposing end them: to die, to sleep.
    William Shakespeare Hamlet
  • All that we see or seem is but a dream within a dream.
    Edgar Allan Poe A Dream Within a Dream
  • It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife.
    Jane Austen Pride and Prejudice
  • Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world.
    Herman Melville Moby-Dick


Install Dependencies

npm i svelte-motion clsx tailwind-merge

Add util file

import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
    return twMerge(clsx(inputs));

Add Tailwind CSS plugin for variable classes

import flattenColorPalette from 'tailwindcss/lib/util/flattenColorPalette';

const config = {
	// ... other properties
	theme: {
		extend: {
			animation: {
				// ...other animations
					'scroll var(--animation-duration, 40s) var(--animation-direction, forwards) linear infinite'
			keyframes: {
				// ... other keyframes
				scroll: {
					to: {
						transform: 'translate(calc(-50% - 0.5rem))'

// This plugin adds each Tailwind color as a global CSS variable, e.g. var(--gray-200).
function addVariablesForColors({ addBase, theme }: any) {
	let allColors = flattenColorPalette(theme('colors'));
	let newVars = Object.fromEntries(
		Object.entries(allColors).map(([key, val]) => [`--${key}`, val])

		':root': newVars

Copy the source code

<script lang="ts">
	import { cn } from '$lib/utils';
	import { onMount } from 'svelte';

	export let items: {
		quote: string;
		name: string;
		title: string;
	export let direction: 'left' | 'right' | undefined = 'left';
	export let speed: 'fast' | 'normal' | 'slow' | undefined = 'fast';
	export let pauseOnHover: boolean | undefined = true;
	export let className: string | undefined = undefined;

	let containerRef: HTMLDivElement;
	let scrollerRef: HTMLUListElement;

	onMount(() => {

	let start = false;

	function addAnimation() {
		if (containerRef && scrollerRef) {
			const scrollerContent = Array.from(scrollerRef.children);

			scrollerContent.forEach((item) => {
				const duplicatedItem = item.cloneNode(true);
				if (scrollerRef) {

			start = true;
	const getDirection = () => {
		if (containerRef) {
			if (direction === 'left') {'--animation-direction', 'forwards');
			} else {'--animation-direction', 'reverse');
	const getSpeed = () => {
		if (containerRef) {
			if (speed === 'fast') {'--animation-duration', '20s');
			} else if (speed === 'normal') {'--animation-duration', '40s');
			} else {'--animation-duration', '80s');

		'scroller relative z-20  max-w-7xl overflow-hidden  [mask-image:linear-gradient(to_right,transparent,white_20%,white_80%,transparent)]',
			' flex w-max min-w-full shrink-0 flex-nowrap gap-4 py-4',
			start && 'animate-scroll ',
			pauseOnHover && 'hover:[animation-play-state:paused]'
		{#each items as item, idx (}
				class="relative w-[350px] max-w-full flex-shrink-0 rounded-2xl border border-b-0 border-slate-700 px-8 py-6 md:w-[450px]"
				style="background: linear-gradient(180deg, var(--slate-800), var(--slate-900));"
						class="user-select-none -z-1 pointer-events-none absolute -left-0.5 -top-0.5 h-[calc(100%_+_4px)] w-[calc(100%_+_4px)]"
					<span class=" relative z-20 text-sm font-normal leading-[1.6] text-gray-100">
					<div class="relative z-20 mt-6 flex flex-row items-center">
						<span class="flex flex-col gap-1">
							<span class=" text-sm font-normal leading-[1.6] text-gray-400">
							<span class=" text-sm font-normal leading-[1.6] text-gray-400">
import InfiniteMovingCards from './InfiniteMovingCards.svelte';

export { InfiniteMovingCards };


Prop Type Description
items {quote: string, name: string, title: string}[] An array of objects, each containing a quote, name, and title.
direction "left" | "right" | undefined Default: "left". The direction of the animation.
speed "fast" | "normal" | "slow" | undefined Default: "fast". The speed of the animation.
pauseOnHover boolean | undefined Default: true. Whether to pause the animation when the mouse hovers over the component.
className string | undefined A class name to apply to the component.