Comparison Feature Section
Original · freeus-vs-them comparison
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/featx-comparison.json"use client";
import { useState } from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
type Category = "cost" | "speed" | "support";
type Filter = "all" | Category;
type Feature = {
id: string;
label: string;
category: Category;
them: string;
us: string;
};
const FILTERS: { id: Filter; label: string }[] = [
{ id: "all", label: "Everything" },
{ id: "cost", label: "Cost" },
{ id: "speed", label: "Speed" },
{ id: "support", label: "Support" },
];
const FEATURES: Feature[] = [
{ id: "pricing", label: "Pricing model", category: "cost", them: "Per-seat fees that climb every hire", us: "One flat rate for the whole team" },
{ id: "trial", label: "Free trial", category: "cost", them: "Credit card required upfront", us: "14 full days, no card" },
{ id: "contract", label: "Commitment", category: "cost", them: "12-month lock-in contract", us: "Month-to-month, cancel anytime" },
{ id: "setup", label: "Setup time", category: "speed", them: "A 2–3 week rollout project", us: "Live in under 10 minutes" },
{ id: "import", label: "Data import", category: "speed", them: "Manual CSV wrangling", us: "One-click sync from day one" },
{ id: "value", label: "Time to first value", category: "speed", them: "Days of configuration", us: "Results the same afternoon" },
{ id: "response", label: "Support response", category: "support", them: "48-hour email queue", us: "Under 2 hours, real people" },
{ id: "onboarding", label: "Onboarding", category: "support", them: "A paid professional-services tier", us: "White-glove, always included" },
{ id: "migration", label: "Migration", category: "support", them: "Do-it-yourself help docs", us: "We move your data for you" },
];
function IconCheck() {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth={2.4} strokeLinecap="round" strokeLinejoin="round">
<path d="M5 13l4 4L19 7" />
</svg>
);
}
function IconMinus() {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth={2.4} strokeLinecap="round">
<path d="M6 12h12" />
</svg>
);
}
function IconArrow() {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" className="h-4 w-4 transition-transform duration-300 group-hover:translate-x-0.5" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
<path d="M5 12h14M13 6l6 6-6 6" />
</svg>
);
}
function NimbusMark() {
return (
<span className="grid h-7 w-7 shrink-0 place-items-center rounded-lg bg-gradient-to-br from-indigo-500 to-violet-600 text-white shadow-sm shadow-indigo-600/30">
<svg viewBox="0 0 24 24" aria-hidden="true" className="h-4 w-4">
<path d="M12 3l1.9 5.6L19.5 10l-5.6 1.5L12 17l-1.9-5.5L4.5 10l5.6-1.4z" fill="currentColor" />
</svg>
</span>
);
}
function LegacyMark() {
return (
<span className="grid h-7 w-7 shrink-0 place-items-center rounded-lg border border-slate-200 bg-slate-100 text-slate-400 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-500">
<svg viewBox="0 0 24 24" aria-hidden="true" className="h-4 w-4" fill="none" stroke="currentColor" strokeWidth={1.8}>
<rect x="4" y="6" width="16" height="4" rx="1" />
<rect x="4" y="14" width="16" height="4" rx="1" />
</svg>
</span>
);
}
export default function FeatxComparison() {
const reduce = useReducedMotion();
const [filter, setFilter] = useState<Filter>("all");
const visible = filter === "all" ? FEATURES : FEATURES.filter((f) => f.category === filter);
const gridCols = "grid grid-cols-[1.05fr_1fr_1.1fr] sm:grid-cols-[1.5fr_1fr_1fr]";
const usCell =
"relative bg-indigo-50/70 dark:bg-indigo-500/[0.08] border-l border-indigo-100/80 dark:border-indigo-500/20";
return (
<section className="relative w-full overflow-hidden bg-gradient-to-b from-white via-white to-slate-50 dark:from-zinc-950 dark:via-zinc-950 dark:to-zinc-900">
<style>{`
@keyframes featx-glow {
0%, 100% { opacity: .35; }
50% { opacity: .9; }
}
@keyframes featx-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-3px); }
}
.featx-glow { animation: featx-glow 3.4s ease-in-out infinite; }
.featx-float { animation: featx-float 4.2s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
.featx-glow, .featx-float { animation: none !important; }
}
`}</style>
<div aria-hidden="true" className="pointer-events-none absolute -top-24 left-1/2 h-72 w-72 -translate-x-1/2 rounded-full bg-indigo-400/20 blur-3xl dark:bg-indigo-500/10" />
<div aria-hidden="true" className="pointer-events-none absolute bottom-0 right-[-6rem] h-80 w-80 rounded-full bg-violet-400/10 blur-3xl dark:bg-violet-500/10" />
<div className="relative mx-auto max-w-6xl px-6 py-24 sm:py-32">
<motion.div
initial={reduce ? false : { opacity: 0, y: 16 }}
whileInView={reduce ? undefined : { opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-80px" }}
transition={{ duration: 0.5, ease: "easeOut" }}
className="mx-auto max-w-3xl text-center"
>
<span className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white/70 px-3 py-1 text-xs font-semibold uppercase tracking-wider text-indigo-600 dark:border-zinc-800 dark:bg-zinc-900/70 dark:text-indigo-400">
<span className="h-1.5 w-1.5 rounded-full bg-indigo-500" />
Nimbus vs. legacy tools
</span>
<h2 className="mt-5 text-balance text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl md:text-5xl dark:text-white">
The switch teams make once
<br className="hidden sm:block" /> and never undo
</h2>
<p className="mx-auto mt-4 max-w-2xl text-pretty text-base text-slate-600 sm:text-lg dark:text-zinc-400">
See exactly how Nimbus stacks up against the legacy tools you are probably still paying for — line by line, no asterisks.
</p>
<div
role="group"
aria-label="Filter comparison by category"
className="mt-8 inline-flex flex-wrap items-center justify-center gap-1 rounded-full border border-slate-200 bg-white/60 p-1.5 shadow-sm backdrop-blur dark:border-zinc-800 dark:bg-zinc-900/60"
>
{FILTERS.map((f) => {
const active = filter === f.id;
return (
<button
key={f.id}
type="button"
aria-pressed={active}
onClick={() => setFilter(f.id)}
className={
active
? "rounded-full bg-slate-900 px-4 py-2 text-sm font-semibold text-white shadow-sm transition focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:bg-white dark:text-zinc-900 dark:focus-visible:ring-offset-zinc-900"
: "rounded-full px-4 py-2 text-sm font-semibold text-slate-600 transition hover:text-slate-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:text-zinc-400 dark:hover:text-white dark:focus-visible:ring-offset-zinc-900"
}
>
{f.label}
</button>
);
})}
</div>
</motion.div>
<motion.div
initial={reduce ? false : { opacity: 0, y: 20 }}
whileInView={reduce ? undefined : { opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-60px" }}
transition={{ duration: 0.55, ease: "easeOut", delay: 0.1 }}
className="mx-auto mt-12 max-w-4xl overflow-hidden rounded-3xl border border-slate-200 bg-white/90 shadow-xl shadow-slate-900/5 backdrop-blur dark:border-zinc-800 dark:bg-zinc-900/60 dark:shadow-black/40"
role="table"
aria-label="Nimbus compared with legacy tools"
>
<div role="row" className={`${gridCols} border-b border-slate-200 dark:border-zinc-800`}>
<div role="columnheader" className="flex items-end px-4 py-5 sm:px-6">
<span className="text-xs font-semibold uppercase tracking-wider text-slate-400 dark:text-zinc-500">
What you get
</span>
</div>
<div role="columnheader" className="flex items-center gap-2.5 px-4 py-5 sm:px-6">
<LegacyMark />
<span className="text-sm font-semibold text-slate-500 dark:text-zinc-400">Legacy tools</span>
</div>
<div role="columnheader" className={`${usCell} flex flex-col gap-2 px-4 py-5 sm:px-6`}>
<span aria-hidden="true" className="featx-glow absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-indigo-400 to-transparent" />
<div className="flex items-center gap-2.5">
<NimbusMark />
<span className="text-sm font-bold text-slate-900 dark:text-white">Nimbus</span>
</div>
<span className="featx-float inline-flex w-fit items-center gap-1.5 rounded-full bg-indigo-600/10 px-2.5 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-indigo-700 dark:bg-indigo-500/15 dark:text-indigo-300">
<span className="h-1 w-1 rounded-full bg-emerald-500" />
Most teams choose this
</span>
</div>
</div>
<div role="rowgroup">
<AnimatePresence mode="popLayout" initial={false}>
{visible.map((f) => (
<motion.div
key={f.id}
role="row"
layout={!reduce}
initial={reduce ? { opacity: 0 } : { opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={reduce ? { opacity: 0 } : { opacity: 0, y: -8 }}
transition={{ duration: reduce ? 0 : 0.25, ease: "easeOut" }}
className={`${gridCols} border-t border-slate-100 first:border-t-0 dark:border-zinc-800/80`}
>
<div role="rowheader" className="flex items-center px-4 py-4 sm:px-6 sm:py-5">
<span className="text-sm font-medium text-slate-700 dark:text-zinc-200">{f.label}</span>
</div>
<div role="cell" className="flex items-start gap-2.5 px-4 py-4 sm:px-6 sm:py-5">
<span className="mt-0.5 grid h-5 w-5 shrink-0 place-items-center rounded-full bg-slate-100 text-slate-400 dark:bg-zinc-800 dark:text-zinc-500">
<IconMinus />
</span>
<span className="text-sm text-slate-500 dark:text-zinc-400">{f.them}</span>
</div>
<div role="cell" className={`${usCell} flex items-start gap-2.5 px-4 py-4 sm:px-6 sm:py-5`}>
<span className="mt-0.5 grid h-5 w-5 shrink-0 place-items-center rounded-full bg-emerald-500 text-white shadow-sm shadow-emerald-500/30">
<IconCheck />
</span>
<span className="text-sm font-medium text-slate-800 dark:text-zinc-100">{f.us}</span>
</div>
</motion.div>
))}
</AnimatePresence>
</div>
</motion.div>
<div className="mt-10 flex flex-col items-center justify-center gap-4 text-center sm:flex-row">
<a
href="#start"
className="group inline-flex items-center gap-2 rounded-full bg-gradient-to-r from-indigo-600 to-violet-600 px-6 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-600/25 transition hover:shadow-indigo-600/40 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-zinc-950"
>
Start free — no card needed
<IconArrow />
</a>
<span className="text-sm text-slate-500 dark:text-zinc-400">
Join <span className="font-semibold text-slate-700 dark:text-zinc-200">4,200+ teams</span> who already switched.
</span>
</div>
</div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →Three Column Icon Grid
OriginalA responsive icon grid that presents six product capabilities as scannable cards, each with an inline SVG icon, title and short description.

Alternating Media Rows
OriginalThree feature rows that alternate a text column with a CSS-built visual panel, walking the reader through a plan, measure and ship story.

Bento Feature Grid
OriginalA bento-style grid mixing one large hero cell, a tall stat cell and several compact cards to give features a clear visual hierarchy.

Tabbed Feature Switcher
OriginalA tabbed feature preview that switches panels using only native radio inputs and Tailwind peer variants, so it works with no JavaScript.

Stat Backed Features
OriginalA feature layout that pairs each capability with a headline metric, so every claim is anchored to a measurable result.

Scroll-reveal bento grid
OriginalAn asymmetric bento feature grid whose cards stagger into view on scroll, lift on hover and feature an animated conic gradient border, shimmering headline, growing bar chart and a masked marquee tag strip.

Sticky scroll steps
OriginalA scroll-linked how-it-works section where a sticky panel cross-fades between steps and a progress ring plus filling timeline track advance as you scroll through the process.

Spotlight hover feature cards
OriginalA responsive feature card grid where each card follows the cursor with a radial spotlight glow, lifts and rotates its icon on hover, reveals a call to action, and sits above a scrolling logo marquee.

Animated marquee highlights band
OriginalA bold feature band with an animated gradient background, floating blurred orbs, blur-in staggered headline, two opposing marquee pill rows and shimmering stat cards.

Bordered Feature Grid (2x2)
MITA two-column grid of bordered feature cards, each pairing an outlined icon with a title and supporting copy. Clean, enterprise-leaning layout with full dark-mode support.

Outlined Feature Cards with CTA
MITA three-column feature section with heading and intro, plus blue-outlined cards that each carry an icon, description, and a circular arrow call-to-action. Fully theme-aware.

Icon-Left Feature List
MITA centered heading over a three-column feature list, each row leading with a rounded icon badge and a 'Learn More' link. Classic marketing feature block, ported with added dark variants.

