# Features Comparison Table — Tailwind Feature Section

> An us-versus-them table with a lifted highlight column; checks pop and draw their stroke row by row as the table scrolls in.

- Category: [Feature Sections](https://pixelpapa.com/components/features.md)
- Tags: features, comparison, table, checks, stagger
- Dependencies: lucide-react, motion (plus React and Tailwind CSS v4)
- Last updated: 2026-11-13
- Live preview: https://pixelpapa.com/components/features/features-comparison-table

## Install

```bash
npx shadcn@latest add "https://pixelpapa.com/r/features-comparison-table.json"
```

The file is written to `components/pixelpapa/features-comparison-table.tsx`. To install manually, run `npm install lucide-react motion` and copy the source below into your project. It imports `cn` from `@/lib/utils`, which every shadcn project has.

## Source

```tsx
"use client";

import { ArrowRight, Minus } from "lucide-react";
import { motion } from "motion/react";
import { cn } from "@/lib/utils";

type Cell = boolean | string;
type Row = { feature: string; hint: string; us: Cell; a: Cell; b: Cell };

const defaultRows: Row[] = [
  { feature: "Live stock across warehouses", hint: "Synced every 60 seconds", us: true, a: false, b: true },
  { feature: "Demand forecast per SKU", hint: "Seasonality and promo aware", us: true, a: "By hand", b: "Add-on" },
  { feature: "Automatic purchase orders", hint: "Drafted when cover drops below target", us: true, a: false, b: true },
  { feature: "Supplier lead-time tracking", hint: "Learns from every delivery", us: true, a: false, b: false },
  { feature: "Landed cost per unit", hint: "Freight, duty and fees included", us: true, a: "By hand", b: true },
  { feature: "Storefront and 3PL connectors", hint: "34 native integrations", us: true, a: false, b: "Paid setup" },
  { feature: "Time to first forecast", hint: "From sign-up to usable numbers", us: "Same day", a: "2–3 weeks", b: "A quarter" },
];

function Mark({ value, delay, strong }: { value: Cell; delay: number; strong?: boolean }) {
  if (typeof value === "string")
    return <span className={cn("text-center text-xs sm:text-sm", strong ? "font-semibold text-primary" : "text-muted-foreground")}>{value}</span>;
  if (!value) return <Minus aria-label="Not included" className="size-4 text-muted-foreground/40" />;
  return (
    <motion.span
      role="img"
      aria-label="Included"
      initial={{ scale: 0.4, opacity: 0 }}
      whileInView={{ scale: 1, opacity: 1 }}
      viewport={{ once: true }}
      transition={{ delay, type: "spring", stiffness: 420, damping: 18 }}
      className={cn(
        "flex size-6 items-center justify-center rounded-full",
        strong ? "bg-primary text-primary-foreground shadow-md shadow-primary/30" : "bg-muted text-muted-foreground",
      )}
    >
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3.5" strokeLinecap="round" strokeLinejoin="round" className="size-3.5">
        <motion.path
          d="M5 12.5l4.5 4.5L19 7.5"
          initial={{ pathLength: 0 }}
          whileInView={{ pathLength: 1 }}
          viewport={{ once: true }}
          transition={{ delay: delay + 0.12, duration: 0.35, ease: "easeOut" }}
        />
      </svg>
    </motion.span>
  );
}

const cols = "grid grid-cols-[1.4fr_1fr_1fr] sm:grid-cols-[1.6fr_1fr_1fr_1fr]";

export default function FeaturesComparisonTable({ rows = defaultRows }: { rows?: Row[] }) {
  return (
    <section className="w-full bg-background px-4 py-20 sm:px-6 sm:py-24">
      <div className="mx-auto max-w-4xl">
        <div className="mx-auto max-w-2xl text-center">
          <p className="text-sm font-medium text-primary">Why teams switch</p>
          <h2 className="mt-3 text-4xl font-semibold tracking-tight text-balance text-foreground sm:text-5xl">
            Built for planners, not for the person who maintains the spreadsheet
          </h2>
        </div>

        <div role="table" aria-label="Plover compared with alternatives" className="relative mt-16">
          {/* Highlight panel sits behind the "us" column; its offsets mirror the fr tracks above. */}
          <motion.div
            aria-hidden
            initial={{ opacity: 0, y: 24 }}
            whileInView={{ opacity: 1, y: 0 }}
            viewport={{ once: true }}
            transition={{ duration: 0.7, ease: [0.22, 1, 0.36, 1] }}
            className="absolute -inset-y-5 left-[41.18%] w-[29.41%] rounded-3xl border border-primary/30 bg-card shadow-2xl shadow-primary/15 sm:left-[34.78%] sm:w-[21.74%]"
          >
            <div className="absolute inset-x-6 top-0 h-px bg-gradient-to-r from-transparent via-primary to-transparent" />
          </motion.div>

          <div role="row" className={cn(cols, "relative items-end pb-5 text-center text-sm font-medium")}>
            <span role="columnheader" className="text-left text-xs font-normal text-muted-foreground">Compare</span>
            <span role="columnheader" className="flex flex-col items-center gap-1 text-foreground">
              <svg viewBox="0 0 24 24" className="size-6 text-primary" fill="currentColor" aria-hidden>
                <path d="M12 12V2a10 10 0 0 1 10 10Zm0 0v10A10 10 0 0 1 2 12Z" />
              </svg>
              Plover
            </span>
            <span role="columnheader" className="text-muted-foreground">Spreadsheets</span>
            <span role="columnheader" className="hidden text-muted-foreground sm:block">Legacy ERP</span>
          </div>

          {rows.map((row, i) => (
            <div
              key={row.feature}
              role="row"
              className={cn(cols, "group relative items-center border-t border-border py-4 transition-colors hover:bg-muted/40")}
            >
              <div role="cell" className="pr-3 pl-1">
                <p className="text-sm font-medium text-foreground">{row.feature}</p>
                <p className="mt-0.5 hidden text-xs text-muted-foreground sm:block">{row.hint}</p>
              </div>
              <div role="cell" className="flex justify-center">
                <Mark value={row.us} delay={0.2 + i * 0.09} strong />
              </div>
              <div role="cell" className="flex justify-center">
                <Mark value={row.a} delay={0.3 + i * 0.09} />
              </div>
              <div role="cell" className="hidden justify-center sm:flex">
                <Mark value={row.b} delay={0.35 + i * 0.09} />
              </div>
            </div>
          ))}

          <div className={cn(cols, "relative border-t border-border pt-5")}>
            <span />
            <a
              href="#"
              className="mx-auto inline-flex h-9 items-center gap-1.5 rounded-full bg-primary px-4 text-sm font-medium text-primary-foreground transition-transform hover:-translate-y-0.5"
            >
              Try it <ArrowRight className="size-3.5" />
            </a>
          </div>
        </div>

        <p className="mt-12 text-center text-xs text-muted-foreground">
          Comparison based on public documentation as of March. Legacy ERP reflects the median of three vendors.
        </p>
      </div>
    </section>
  );
}
```

## AI prompt

```text
Add the "Features Comparison Table" component from PixelPapa UI to this project.

What it is: An us-versus-them table with a lifted highlight column; checks pop and draw their stroke row by row as the table scrolls in.
Category: Feature Sections. Depends on: lucide-react, motion.

1. Install it with the shadcn CLI:
   npx shadcn@latest add "https://pixelpapa.com/r/features-comparison-table.json"
   (If the CLI is unavailable, fetch that URL, write files[0].content to components/pixelpapa/features-comparison-table.tsx and install the listed dependencies.)
2. Import it from "@/components/pixelpapa/features-comparison-table" and place it where I describe below.
3. Keep its animation timings and accessibility attributes. Replace the demo copy and data with content that fits my product, and use my existing theme tokens (bg-background, text-foreground, bg-primary, border-border) rather than hard-coded colours.

Where to use it: <describe the page and position here>
```

## More feature sections

- [Features Accordion Visual](https://pixelpapa.com/components/features/features-accordion-visual.md) (Pro): An accordion on the left steers a product mock on the right: a spring-driven focus frame glides to the matching region while the rest dims.
- [Features Alternating Rows](https://pixelpapa.com/components/features/features-alternating-rows.md) (Pro): Three text-and-illustration rows that swap sides; each drawing animates in on view and its floating layer drifts with scroll.
- [Features Animated Workflow](https://pixelpapa.com/components/features/features-animated-workflow.md) (Pro): A four-stage pipeline where an order token hops card to card with a shared-layout spring, leaving a completed result behind at every stage.
- [Features Big Numbered List](https://pixelpapa.com/components/features/features-big-numbered-list.md): An editorial list with oversized serif numerals that rise through a mask on view; the hovered or focused row opens its note and a marker bar slides to it.
- [Features Code And Result](https://pixelpapa.com/components/features/features-code-and-result.md) (Pro): A code snippet with clickable values beside a live preview: cycle a prop in the code and the toast on the right springs in with the new tone and position.
- [Features Icon Grid Hover](https://pixelpapa.com/components/features/features-icon-grid-hover.md): A hairline grid of six features: grid lines glow under the cursor, a beam laps the hovered cell's border and a detail row slides up.
