slidecn
uicomparison-slidesource: ok · refreshed
Comparison Slide
A side-by-side comparison slide with columns or table variant.
npx shadcn add "https://slidecn.vercel.app/r/comparison-slide.json"free
Where it goes
data-table
Use cases
dashboard
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · light@revealjs/reactreveal.js
Files1 · source cached 10/7/2026, 4:06:14 AM
registry/slidecn/ui/comparison-slide.tsx
"use client";
import { Slide } from "@revealjs/react";
import type { SlideProps } from "@revealjs/react";
import { cva } from "class-variance-authority";
import type { VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const comparisonSlideVariants = cva("h-full", {
defaultVariants: {
variant: "columns",
},
variants: {
variant: {
columns: "grid grid-cols-2 gap-6",
table: "",
},
},
});
const headerColorVariants: Record<string, string> = {
default: "bg-muted text-foreground",
green: "bg-green-500/10 text-green-600 dark:text-green-400",
red: "bg-red-500/10 text-red-600 dark:text-red-400",
};
interface ComparisonColumn {
label: string;
items: string[];
color?: "default" | "green" | "red";
}
interface ComparisonSlideProps
extends SlideProps, VariantProps<typeof comparisonSlideVariants> {
title?: string;
left: ComparisonColumn;
right: ComparisonColumn;
notes?: string;
}
const ComparisonSlide = ({
title,
left,
right,
variant,
className,
notes,
...slideProps
}: ComparisonSlideProps) => {
if (variant === "table") {
return (
<Slide className={cn(className)} {...slideProps}>
<div className="flex h-full flex-col gap-6">
{title && (
<h2 className="text-3xl font-bold tracking-tight">{title}</h2>
)}
<table className="w-full border-collapse text-left">
<thead>
<tr>
<th
className={cn(
"rounded-tl-lg px-4 py-3 text-sm font-semibold",
headerColorVariants[left.color ?? "default"]
)}
>
{left.label}
</th>
<th
className={cn(
"rounded-tr-lg px-4 py-3 text-sm font-semibold",
headerColorVariants[right.color ?? "default"]
)}
>
{right.label}
</th>
</tr>
</thead>
<tbody>
{Array.from({
length: Math.max(left.items.length, right.items.length),
}).map((_, i) => (
<tr key={i} className="border-b border-border odd:bg-muted/30">
<td className="px-4 py-2.5 text-sm">{left.items[i] ?? ""}</td>
<td className="px-4 py-2.5 text-sm">
{right.items[i] ?? ""}
</td>
</tr>
))}
</tbody>
</table>
</div>
{notes && <aside className="notes">{notes}</aside>}
</Slide>
);
}
const renderColumn = (col: ComparisonColumn) => (
<div className="flex flex-col rounded-lg border">
<div
className={cn(
"rounded-t-lg px-4 py-3 text-sm font-semibold",
headerColorVariants[col.color ?? "default"]
)}
>
{col.label}
</div>
<div className="flex flex-col gap-2 p-4">
{col.items.map((item, i) => (
<div key={i} className="flex items-start gap-2 text-sm">
<span className="mt-1.5 size-1.5 shrink-0 rounded-full bg-muted-foreground/30" />
<span>{item}</span>
</div>
))}
</div>
</div>
);
return (
<Slide className={cn(className)} {...slideProps}>
<div className="flex h-full flex-col gap-6">
{title && (
<h2 className="text-3xl font-bold tracking-tight">{title}</h2>
)}
<div className={cn(comparisonSlideVariants({ variant }))}>
{renderColumn(left)}
{renderColumn(right)}
</div>
</div>
{notes && <aside className="notes">{notes}</aside>}
</Slide>
);
};
export { ComparisonSlide, comparisonSlideVariants };