# Striped Comparison Table With Sticky Header

A shadcn/ui block from the [@weinc](https://we.inc/components?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/weinc/striped-comparison-table-with-sticky-header).

- Page, with a live preview: https://shadcn.exchange/r/weinc/striped-comparison-table-with-sticky-header
- Screenshot: https://shadcn.exchange/shot/weinc/striped-comparison-table-with-sticky-header/light.png
- Registry item: https://we.inc/r/striped-comparison-table-with-sticky-header.json
- Listed: 2026-10-29, source last changed 2026-10-29

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/weinc/striped-comparison-table-with-sticky-header.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Files

### `components/we-inc/striped-comparison-table-with-sticky-header.tsx`

```tsx
// A striped plan comparison inside a scrolling box: the header row sticks while you scroll.
const plans = ["Commuter", "Tourer", "Cargo"];
const rows: [string, (string | boolean)[]][] = [
  ["Range on one charge", ["60 km", "110 km", "85 km"]],
  ["Motor", ["250 W hub", "250 W mid-drive", "250 W mid-drive"]],
  ["Weight", ["19 kg", "23 kg", "34 kg"]],
  ["Gears", ["Single speed", "Belt hub, 8", "Belt hub, 5"]],
  ["Integrated lights", [true, true, true]],
  ["Rear rack", [false, true, true]],
  ["Front cargo box", [false, false, true]],
  ["Suspension fork", [false, true, false]],
  ["Hydraulic disc brakes", [true, true, true]],
  ["App lock and tracking", [false, true, true]],
  ["Max rider and load", ["120 kg", "130 kg", "200 kg"]],
  ["Warranty", ["2 years", "3 years", "3 years"]],
  ["Price", ["£1,290", "£2,450", "£3,180"]],
];

function Mark({ on }: { on: boolean }) {
  return on ? (
    <span className="inline-flex items-center gap-1.5 text-teal-800"><svg viewBox="0 0 16 16" className="h-4 w-4" fill="none" stroke="currentColor" strokeWidth="2.2" aria-hidden="true"><path d="m3.5 8.5 3 3 6-7" strokeLinecap="round" strokeLinejoin="round" /></svg><span className="sr-only">Included</span></span>
  ) : (
    <span className="inline-flex items-center text-slate-400"><svg viewBox="0 0 16 16" className="h-4 w-4" stroke="currentColor" strokeWidth="2" aria-hidden="true"><path d="M4 8h8" strokeLinecap="round" /></svg><span className="sr-only">Not included</span></span>
  );
}

export default function StripedComparisonTableWithStickyHeader() {
  return (
    <section className="bg-white px-5 py-14 sm:py-20">
      <div className="mx-auto max-w-4xl">
        <h2 className="text-2xl font-semibold tracking-tight text-slate-900 sm:text-3xl">Compare Velomarsh e-bikes</h2>
        <p className="mt-2 text-[15px] text-slate-600">Scroll the table; the model names stay at the top.</p>
        <div className="mt-8 max-h-[420px] overflow-auto rounded-xl border border-slate-200" tabIndex={0} role="region" aria-label="E-bike comparison, scrollable">
          <table className="w-full border-separate border-spacing-0 text-left text-sm">
            <caption className="sr-only">Velomarsh e-bike models compared feature by feature</caption>
            <thead>
              <tr>
                <th scope="col" className="sticky left-0 top-0 z-20 bg-slate-900 px-3 py-3 sm:px-4.5 font-medium text-slate-300">Feature</th>
                {plans.map((p) => (
                  <th key={p} scope="col" className="sticky top-0 z-10 bg-slate-900 px-3 py-3 sm:px-4.5 text-base font-semibold text-white">{p}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {rows.map(([label, vals], i) => (
                <tr key={label} className={i % 2 ? "bg-white" : "bg-teal-50/60"}>
                  <th scope="row" className={`sticky left-0 z-[1] px-3 py-3 sm:px-4 font-medium text-slate-900 ${i % 2 ? "bg-white" : "bg-[#f3faf9]"}`}>{label}</th>
                  {vals.map((v, j) => (
                    <td key={j} className={`px-3 py-3 sm:px-4 ${label === "Price" ? "font-semibold text-slate-900" : "text-slate-700"}`}>
                      {typeof v === "boolean" ? <Mark on={v} /> : v}
                    </td>
                  ))}
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    </section>
  );
}
```
