See how we compare to the competition
We offer more features, better performance, and superior support at a competitive price.
Our Product
The complete solution
Competitor A
Legacy platform
Competitor B
Basic features
Core Features
Real-time collaboration
Work together seamlessly
Advanced analytics
Deep insights and reporting
Custom workflows
Automate your processes
API access
Full REST API
Integration
Third-party integrations
Webhooks
Custom integrations
Security
SSO & SAML
2FA authentication
SOC 2 compliance
Audit logs
Support
24/7 support
Dedicated account manager
SLA guarantee
Performance
Response time
Uptime
Global CDN
Features
- ✅ Competitive comparison - Compare against 2 competitors
- ✅ Winner indicators - Visual badges showing who wins each feature
- ✅ Score summary - Crown icon and win count for the leader
- ✅ Category grouping - Organize features by category
- ✅ Tooltips - Hover info icons for additional context
- ✅ Column headers - Beautiful headers with icons and badges
- ✅ Flexible values - Boolean, string, or custom React nodes
- ✅ Highlighted column - Mark your product as "That's us!"
- ✅ Feature descriptions - Optional descriptions for clarity
- ✅ Visual hierarchy - Winning rows highlighted with blue tint
- ✅ Responsive design - Mobile-friendly with column selector
- ✅ Hover effects - Smooth row highlighting
- ✅ TypeScript support - Full type safety
- ✅ Customizable - Easy to style and extend
Installation
Copy and paste the following code into your project.
import { Check, X, Minus, LucideIcon, Crown, Info } from "lucide-react";
import { ReactNode } from "react";
interface ComparisonColumn {
name: string;
description?: string;
icon?: LucideIcon;
highlighted?: boolean;
badge?: string;
}
interface ComparisonFeature {
category?: string;
name: string;
description?: string;
tooltip?: string;
yours: boolean | string | ReactNode;
competitorA: boolean | string | ReactNode;
competitorB: boolean | string | ReactNode;
winner?: "yours" | "competitorA" | "competitorB" | "tie";
}
interface FeatureComparisonProps {
headline?: string;
description?: string;
columns: {
yours: ComparisonColumn;
competitorA: ComparisonColumn;
competitorB: ComparisonColumn;
};
features: ComparisonFeature[];
showWinnerBadges?: boolean;
showScoreSummary?: boolean;
}
export function FeatureComparison({
headline = "See how we compare",
description,
columns,
features,
showWinnerBadges = true,
showScoreSummary = true,
}: FeatureComparisonProps) {
const scores = {
yours: features.filter((f) => f.winner === "yours").length,
competitorA: features.filter((f) => f.winner === "competitorA").length,
competitorB: features.filter((f) => f.winner === "competitorB").length,
};
const renderValue = (
value: boolean | string | ReactNode,
isWinner: boolean = false,
) => {
if (typeof value === "boolean") {
return (
<div
className={`inline-flex items-center justify-center rounded-full p-1 ${
isWinner && value ? "bg-emerald-500/10" : ""
}`}
>
{value ? (
<Check
className={`h-5 w-5 ${
isWinner ? "text-emerald-400" : "text-emerald-500/70"
}`}
/>
) : (
<X className="h-5 w-5 text-zinc-600" />
)}
</div>
);
}
if (value === "-" || value === "—") {
return <Minus className="h-5 w-5 text-zinc-600" />;
}
return (
<span
className={`text-sm font-medium ${
isWinner ? "text-white" : "text-zinc-400"
}`}
>
{value}
</span>
);
};
const ColumnHeader = ({
column,
type,
score,
}: {
column: ComparisonColumn;
type: "yours" | "competitorA" | "competitorB";
score?: number;
}) => {
const Icon = column.icon;
const isYours = type === "yours";
const isLeader =
showScoreSummary && score === Math.max(...Object.values(scores));
return (
<div
className={`relative rounded-2xl border p-6 transition-all ${
column.highlighted || isYours
? "border-blue-500/50 bg-gradient-to-b from-blue-500/10 to-transparent"
: "border-zinc-800/50 bg-zinc-950"
}`}
>
{(column.badge || isYours) && (
<div className="absolute -top-3 left-1/2 -translate-x-1/2">
<span className="rounded-full bg-blue-500 px-3 py-1 text-xs font-semibold text-white">
{column.badge || "That's us!"}
</span>
</div>
)}
<div className="text-center">
{Icon && (
<div className="mb-4 flex justify-center">
<div
className={`rounded-lg p-3 ${
isYours ? "bg-blue-500/10" : "bg-zinc-800/50"
}`}
>
<Icon
className={`h-6 w-6 ${
isYours ? "text-blue-400" : "text-zinc-500"
}`}
/>
</div>
</div>
)}
<h3 className="text-xl font-bold text-white">{column.name}</h3>
{column.description && (
<p className="mt-2 text-sm text-zinc-500">{column.description}</p>
)}
{showScoreSummary && score !== undefined && (
<div className="mt-4 flex items-center justify-center gap-2">
{isLeader && <Crown className="h-4 w-4 text-yellow-500" />}
<div className="rounded-full bg-zinc-900 px-3 py-1">
<span className="text-sm font-bold text-white">{score}</span>
<span className="ml-1 text-xs text-zinc-500">wins</span>
</div>
</div>
)}
</div>
</div>
);
};
const groupedFeatures = features.reduce(
(acc, feature) => {
const category = feature.category || "Features";
if (!acc[category]) {
acc[category] = [];
}
acc[category].push(feature);
return acc;
},
{} as Record<string, ComparisonFeature[]>,
);
return (
<section className="w-full bg-black py-12 sm:py-16 lg:py-20 z-30">
<div className="container px-4 md:px-6">
<div className="mx-auto max-w-3xl space-y-4 text-center">
<h2 className="text-3xl font-bold tracking-tighter text-white sm:text-4xl md:text-5xl">
{headline}
</h2>
{description && (
<p className="text-lg text-zinc-400 md:text-xl">{description}</p>
)}
</div>
<div className="mx-auto mt-12 max-w-7xl">
<div className="hidden lg:grid lg:grid-cols-4 lg:gap-6">
<div className="p-6" />
<ColumnHeader
column={columns.yours}
type="yours"
score={scores.yours}
/>
<ColumnHeader
column={columns.competitorA}
type="competitorA"
score={scores.competitorA}
/>
<ColumnHeader
column={columns.competitorB}
type="competitorB"
score={scores.competitorB}
/>
</div>
<div className="mt-8 overflow-hidden rounded-2xl border border-zinc-800/50 bg-zinc-950">
<div className="border-b border-zinc-800/50 p-4 lg:hidden">
<select className="w-full rounded-lg border border-zinc-800 bg-zinc-900 px-4 py-2 text-white">
<option>{columns.yours.name}</option>
<option>{columns.competitorA.name}</option>
<option>{columns.competitorB.name}</option>
</select>
</div>
{Object.entries(groupedFeatures).map(
([category, categoryFeatures]) => (
<div key={category}>
<div className="border-b border-zinc-800/50 bg-zinc-900/50 px-6 py-3">
<h4 className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
{category}
</h4>
</div>
<div className="divide-y divide-zinc-800/50">
{categoryFeatures.map((feature, index) => {
const hasWinner = feature.winner && showWinnerBadges;
return (
<div
key={index}
className={`group relative grid grid-cols-1 gap-4 p-6 transition-all lg:grid-cols-4 lg:items-center ${
hasWinner && feature.winner === "yours"
? "bg-blue-500/5 hover:bg-blue-500/10"
: "hover:bg-zinc-900/30"
}`}
>
{hasWinner && feature.winner === "yours" && (
<div className="absolute -left-1 top-1/2 hidden h-full w-1 -translate-y-1/2 rounded-r bg-blue-500 lg:block" />
)}
<div className="flex items-start gap-2">
<div className="flex-1">
<h5 className="font-semibold text-white">
{feature.name}
</h5>
{feature.description && (
<p className="mt-1 text-sm text-zinc-500">
{feature.description}
</p>
)}
</div>
{feature.tooltip && (
<div className="group/tooltip relative">
<Info className="h-4 w-4 text-zinc-600 transition-colors hover:text-zinc-400" />
<div className="pointer-events-none absolute left-1/2 top-full z-10 mt-2 hidden w-48 -translate-x-1/2 rounded-lg border border-zinc-800 bg-zinc-900 p-2 text-xs text-zinc-400 shadow-xl group-hover/tooltip:block">
{feature.tooltip}
</div>
</div>
)}
</div>
<div
className={`flex items-center justify-center lg:justify-center ${
hasWinner && feature.winner === "yours"
? "rounded-lg bg-blue-500/10 p-2"
: ""
}`}
>
<div className="mr-auto text-sm text-zinc-500 lg:hidden">
{columns.yours.name}:
</div>
{renderValue(
feature.yours,
feature.winner === "yours",
)}
</div>
<div
className={`flex items-center justify-center lg:justify-center ${
hasWinner && feature.winner === "competitorA"
? "rounded-lg bg-zinc-800/30 p-2"
: ""
}`}
>
<div className="mr-auto text-sm text-zinc-500 lg:hidden">
{columns.competitorA.name}:
</div>
{renderValue(
feature.competitorA,
feature.winner === "competitorA",
)}
</div>
<div
className={`flex items-center justify-center lg:justify-center ${
hasWinner && feature.winner === "competitorB"
? "rounded-lg bg-zinc-800/30 p-2"
: ""
}`}
>
<div className="mr-auto text-sm text-zinc-500 lg:hidden">
{columns.competitorB.name}:
</div>
{renderValue(
feature.competitorB,
feature.winner === "competitorB",
)}
</div>
</div>
);
})}
</div>
</div>
),
)}
</div>
</div>
</div>
</section>
);
}Usage
Basic Usage
import FeatureComparison from "@/components/ui/feature-comparison";
import { Zap, Building2, Users } from "lucide-react";
export default function Page() {
return (
<FeatureComparison
headline="See how we compare"
description="We offer more features at a better price."
columns={{
yours: {
name: "Our Product",
icon: Zap,
},
competitorA: {
name: "Competitor A",
icon: Building2,
},
competitorB: {
name: "Competitor B",
icon: Users,
},
}}
features={[
{
name: "Real-time collaboration",
yours: true,
competitorA: true,
competitorB: false,
},
{
name: "API access",
yours: "Unlimited",
competitorA: "Limited",
competitorB: false,
},
]}
/>
);
}With Categories
<FeatureComparison
columns={{
yours: { name: "Us" },
competitorA: { name: "Them A" },
competitorB: { name: "Them B" },
}}
features={[
{
category: "Core Features",
name: "Advanced analytics",
yours: true,
competitorA: "Limited",
competitorB: false,
},
{
category: "Core Features",
name: "Custom workflows",
yours: true,
competitorA: false,
competitorB: false,
},
{
category: "Security",
name: "SSO & SAML",
yours: true,
competitorA: "Enterprise only",
competitorB: false,
},
]}
/>With Descriptions
<FeatureComparison
columns={{
yours: {
name: "Our Product",
description: "The complete solution",
icon: Zap,
highlighted: true,
},
competitorA: {
name: "Competitor A",
description: "Legacy platform",
icon: Building2,
},
competitorB: {
name: "Competitor B",
description: "Basic features",
icon: Users,
},
}}
features={[
{
category: "Performance",
name: "Response time",
description: "Average API response time",
yours: "<50ms",
competitorA: "<200ms",
competitorB: "<500ms",
},
]}
/>With Winner Indicators
<FeatureComparison
columns={{
yours: { name: "Our Product" },
competitorA: { name: "Competitor A" },
competitorB: { name: "Competitor B" },
}}
features={[
{
name: "API access",
yours: "Unlimited",
competitorA: "Limited",
competitorB: "Basic",
winner: "yours", // Highlights this row
},
{
name: "24/7 support",
yours: true,
competitorA: "Business hours",
competitorB: false,
winner: "yours",
},
]}
showWinnerBadges={true} // Default: true
showScoreSummary={true} // Default: true
/>With Tooltips
<FeatureComparison
columns={{
yours: { name: "Us" },
competitorA: { name: "Them A" },
competitorB: { name: "Them B" },
}}
features={[
{
name: "Advanced analytics",
description: "Deep insights and reporting",
tooltip: "Custom dashboards, export reports, and real-time metrics",
yours: true,
competitorA: "Limited",
competitorB: false,
winner: "yours",
},
]}
/>Custom Badges
<FeatureComparison
columns={{
yours: {
name: "Our Product",
badge: "Best Value",
},
competitorA: {
name: "Competitor A",
},
competitorB: {
name: "Competitor B",
},
}}
features={[...]}
/>Props
FeatureComparisonProps
| Prop | Type | Default | Description |
|---|---|---|---|
headline | string | "See how we compare" | Section headline |
description | string | undefined | Optional description |
columns | Columns | Required | Column configuration object |
features | ComparisonFeature[] | Required | Array of features to compare |
showWinnerBadges | boolean | true | Show visual winner indicators |
showScoreSummary | boolean | true | Show win count and crown icon |
ComparisonColumn
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | Required | Column name |
description | string | undefined | Column description |
icon | LucideIcon | undefined | Column icon |
highlighted | boolean | false | Highlight column |
badge | string | undefined | Custom badge text |
ComparisonFeature
| Prop | Type | Default | Description |
|---|---|---|---|
category | string | "Features" | Feature category |
name | string | Required | Feature name |
description | string | undefined | Feature description |
tooltip | string | undefined | Hover tooltip with extra info |
yours | boolean | string | ReactNode | Required | Your product value |
competitorA | boolean | string | ReactNode | Required | Competitor A value |
competitorB | boolean | string | ReactNode | Required | Competitor B value |
winner | "yours" | "competitorA" | "competitorB" | "tie" | undefined | Mark the winner of this feature |
TypeScript Interface
interface FeatureComparisonProps {
headline?: string;
description?: string;
columns: {
yours: ComparisonColumn;
competitorA: ComparisonColumn;
competitorB: ComparisonColumn;
};
features: ComparisonFeature[];
showWinnerBadges?: boolean;
showScoreSummary?: boolean;
}
interface ComparisonColumn {
name: string;
description?: string;
icon?: LucideIcon;
highlighted?: boolean;
badge?: string;
}
interface ComparisonFeature {
category?: string;
name: string;
description?: string;
tooltip?: string;
yours: boolean | string | ReactNode;
competitorA: boolean | string | ReactNode;
competitorB: boolean | string | ReactNode;
winner?: "yours" | "competitorA" | "competitorB" | "tie";
}Value Types
Features support three value types:
- Boolean: Renders checkmark (✓) or X (✗)
- String: Displays as text (e.g., "Unlimited", "Enterprise only")
- ReactNode: Custom JSX for complex values
Advanced Features
Winner Indicators
Set the winner field on any feature to visually highlight who wins:
- Blue left border on winning rows
- Highlighted background for your wins
- Brighter text for winning values
- Dimmed text for losing values
Score Summary
When enabled (default), shows:
- Crown icon (👑) on the column with most wins
- Win count badge on each column header
- Automatic calculation of scores
Tooltips
Add a tooltip field to any feature to show additional information on hover. Perfect for explaining complex features or providing context.
Category Grouping
Features are automatically grouped by the category field. Categories appear as section headers in the table. If no category is specified, features are grouped under "Features".
Responsive Behavior
- Desktop (lg+): Side-by-side comparison with column headers
- Mobile: Stacked layout with column selector dropdown
Use Cases
Perfect for:
- Competitive analysis pages
- Product comparison tables
- "Why choose us" sections
- Feature differentiation
- Sales enablement
- Marketing landing pages
- Competitor benchmarking
- Product positioning