This guide shows how to set up the marquee component in a new project using separate CSS files imported into globals.css (without using tailwind.config.ts).
src/
├── app/
│ └── globals.css
├── components/
│ ├── ui/
│ │ ├── marquee.tsx
│ │ └── button.tsx
│ └── wizard.tsx
├── styles/
│ ├── marquee.css
│ ├── animations.css (optional)
│ └── components.css (optional)
└── lib/
└── utils.ts
Create src/styles/marquee.css:
/* Marquee Animation Keyframes */
@keyframes marquee {
from {
transform: translateX(0);
}
to {
transform: translateX(calc(-100% - var(--gap)));
}
}
@keyframes marquee-reverse {
from {
transform: translateX(calc(-100% - var(--gap)));
}
to {
transform: translateX(0);
}
}
/* Base Marquee Classes */
.marquee {
animation: marquee var(--duration) infinite linear;
}
.marquee-reverse {
animation: marquee-reverse var(--duration) infinite linear;
}
/* Interactive States */
.marquee-pause-on-hover:hover .marquee,
.marquee-pause-on-hover:hover .marquee-reverse {
animation-play-state: paused;
}
/* Default Variables */
.marquee-container {
--gap: 1rem;
--duration: 40s;
}
/* Responsive Durations */
@media (max-width: 768px) {
.marquee-container {
--duration: 30s;
}
}
@media (max-width: 480px) {
.marquee-container {
--duration: 20s;
}
}
/* Utility Classes */
.marquee-slow {
--duration: 60s;
}
.marquee-fast {
--duration: 20s;
}
.marquee-gap-sm {
--gap: 0.5rem;
}
.marquee-gap-lg {
--gap: 2rem;
}Add the import to your src/app/globals.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Import custom styles */
@import '../styles/marquee.css';
/* Your existing global styles below */
@layer base {
:root {
--background: 0 0% 98.8%;
--foreground: 240 10% 3.9%;
/* ... other CSS variables */
}
}
/* Rest of your global styles */Create src/components/ui/marquee.tsx:
import { cn } from "@/lib/utils";
import React from "react";
interface MarqueeProps {
className?: string;
reverse?: boolean;
pauseOnHover?: boolean;
children?: React.ReactNode;
vertical?: boolean;
repeat?: number;
[key: string]: any;
}
export default function Marquee({
className,
reverse,
pauseOnHover = false,
children,
vertical = false,
repeat = 4,
...props
}: MarqueeProps) {
return (
<div
{...props}
className={cn(
"group flex overflow-hidden p-2 [--duration:40s] [--gap:1rem]",
{
"flex-row": !vertical,
"flex-col": vertical,
},
"marquee-container",
className
)}
>
{Array(repeat)
.fill(0)
.map((_, i) => (
<div
key={i}
className={cn(
"flex shrink-0 justify-around",
{
"animate-marquee flex-row": !vertical,
"animate-marquee-vertical flex-col": vertical,
"group-hover:[animation-play-state:paused]": pauseOnHover,
"[animation-direction:reverse]": reverse,
},
!vertical ? "marquee" : "marquee-vertical",
pauseOnHover && "marquee-pause-on-hover",
reverse && "marquee-reverse"
)}
style={{
gap: "var(--gap)",
}}
>
{children}
</div>
))}
</div>
);
}Create src/lib/utils.ts:
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}pnpm install clsx tailwind-merge lucide-reactCreate src/components/wizard.tsx:
import React from "react";
import { Button } from "@/components/ui/button";
import Marquee from "@/components/ui/marquee";
import { cn } from "@/lib/utils";
import {
KeyRound,
FileText,
LayoutDashboard,
// ... other icons
} from "lucide-react";
const features = [
{ name: "Authentication", icon: KeyRound },
{ name: "Document Processing", icon: FileText },
{ name: "Analytics Dashboard", icon: LayoutDashboard },
// ... other features
];
const FeatureCard = ({ icon: Icon, name }: { icon: React.ElementType; name: string }) => {
return (
<figure className={cn(
"relative min-w-fit cursor-pointer overflow-hidden rounded-xl border p-4",
"border-gray-950/[.1] bg-gray-950/[.01] hover:bg-gray-950/[.05]",
"dark:border-gray-50/[.1] dark:bg-gray-50/[.10] dark:hover:bg-gray-50/[.15]"
)}>
<div className="flex flex-col items-start gap-3">
<Icon size={24} className="text-gray-900 dark:text-gray-100" />
<figcaption className="text-base font-medium text-gray-900 dark:text-gray-100 whitespace-nowrap">
{name}
</figcaption>
</div>
</figure>
);
};
const Wizard = () => {
const midPoint = Math.ceil(features.length / 2);
const firstRow = features.slice(0, midPoint);
const secondRow = features.slice(midPoint);
return (
<div className="space-y-6">
<div className="mx-auto flex max-w-full flex-col items-center space-y-4 text-center">
<h2 className="font-heading text-3xl leading-[1.1] sm:text-3xl md:text-6xl">
Wizard
</h2>
<p className="max-w-[85%] leading-normal text-muted-foreground sm:text-lg sm:leading-7">
Prototype a business automation solution using wizard. Choose the features and design,
and get real-time cost and delivery estimates.
</p>
<div className="relative w-full max-w-5xl lg:max-w-7xl h-[250px] overflow-hidden">
<div className="absolute inset-0">
<div className="relative h-full flex flex-col justify-center gap-4">
<Marquee pauseOnHover className="[--duration:40s]">
{firstRow.map((feature) => (
<FeatureCard key={feature.name} {...feature} />
))}
</Marquee>
<Marquee reverse pauseOnHover className="[--duration:40s] -mt-4">
{secondRow.map((feature) => (
<FeatureCard key={feature.name} {...feature} />
))}
</Marquee>
<div className="pointer-events-none absolute inset-y-0 left-0 w-1/6 bg-gradient-to-r from-background"></div>
<div className="pointer-events-none absolute inset-y-0 right-0 w-1/6 bg-gradient-to-l from-background"></div>
</div>
</div>
</div>
<Button size="lg" className="px-8 mt-8">
Launch Wizard
</Button>
</div>
</div>
);
};
export default Wizard;You can create additional CSS files for better organization:
/* Additional animations */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.animate-fade-in {
animation: fadeIn 0.5s ease-out;
}
.animate-slide-up {
animation: slideUp 0.6s ease-out;
}/* Component-specific styles */
.feature-card {
transition: all 0.2s ease-in-out;
}
.feature-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}Then import them in globals.css:
@import '../styles/marquee.css';
@import '../styles/animations.css';
@import '../styles/components.css';<Marquee>
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</Marquee><Marquee reverse pauseOnHover className="[--duration:30s]">
{items.map(item => <Card key={item.id} {...item} />)}
</Marquee><Marquee className="marquee-fast [--gap:2rem]">
{content}
</Marquee>- No config dependencies - Works without
tailwind.config.ts - Modular - Easy to maintain separate CSS files
- Customizable - Easy to override CSS variables
- Responsive - Built-in responsive behavior
- Performance - CSS animations are hardware accelerated
- Flexible - Easy to add new animation variants
- Animation not working: Ensure CSS is imported in correct order
- Styling conflicts: Check CSS specificity and Tailwind purging
- Performance issues: Reduce repeat count or optimize content
- Responsive issues: Test CSS custom properties on different screens