Tailwind + JSMIT licensed

Tailwind Invoice History Table

A billing-history table with zero-JS status filter chips, row selection that tints the row and raises a bulk-action bar, a native indeterminate select-all, status pills for Paid, Open, Failed and Refunded, and per-row download buttons that cycle idle → generating → done before handing over a real file.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section data-tsp04 class="tsp-04 group/inv w-full min-h-svh bg-tsp04-bg px-4 py-10 font-tsp04-sans text-tsp04-ink antialiased sm:px-8 sm:py-14" aria-labelledby="tsp-04-h">
  <div class="mx-auto grid w-full max-w-6xl gap-6">
    <header class="flex flex-wrap items-end justify-between gap-4">
      <div><h1 id="tsp-04-h" class="text-3xl font-bold tracking-tight">Invoices</h1><p class="mt-1 text-tsp04-mut">Sent to <strong class="font-semibold text-tsp04-ink">billing@northwind.co</strong> · <a href="#tsp-04-h" class="font-semibold text-tsp04-acc underline-offset-4 hover:underline focus-visible:outline-2 focus-visible:outline-tsp04-acc">Change</a></p></div>
      <label class="grid gap-1 text-sm"><span class="sr-only">Year</span><select class="min-h-11 rounded-xl border border-tsp04-line bg-white px-3 text-sm font-medium shadow-xs outline-none focus:ring-4 focus:ring-tsp04-acc/15"><option>2026</option><option>2025</option><option>2024</option></select></label>
    </header>

    <div role="alert" class="flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-800">
      <p class="flex items-center gap-2"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 flex-none"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg><span><strong class="font-semibold">INV-2026-008 failed.</strong> Visa ending 4242 was declined on Aug 1.</span></p>
      <a href="#tsp-04-h" class="inline-flex min-h-11 items-center rounded-xl bg-rose-600 px-4 font-semibold text-white transition hover:bg-rose-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-rose-600">Update payment method</a>
    </div>

    <div class="flex flex-wrap items-center justify-between gap-3">
      <fieldset class="flex flex-wrap gap-2">
        <legend class="sr-only">Filter by status</legend>
        <label class="inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-full border border-tsp04-line bg-white px-4 text-sm font-semibold text-tsp04-mut shadow-xs transition hover:text-tsp04-ink has-checked:border-transparent has-checked:bg-tsp04-ink has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-tsp04-acc"><input type="radio" name="tsp-04-f" id="tsp-04-f-all" class="sr-only" checked>All <span class="tabular-nums opacity-60">8</span></label>
        <label class="inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-full border border-tsp04-line bg-white px-4 text-sm font-semibold text-tsp04-mut shadow-xs transition hover:text-tsp04-ink has-checked:border-transparent has-checked:bg-tsp04-ink has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-tsp04-acc"><input type="radio" name="tsp-04-f" id="tsp-04-f-paid" class="sr-only"><span class="size-1.5 rounded-full bg-emerald-500"></span>Paid <span class="tabular-nums opacity-60">5</span></label>
        <label class="inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-full border border-tsp04-line bg-white px-4 text-sm font-semibold text-tsp04-mut shadow-xs transition hover:text-tsp04-ink has-checked:border-transparent has-checked:bg-tsp04-ink has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-tsp04-acc"><input type="radio" name="tsp-04-f" id="tsp-04-f-open" class="sr-only"><span class="size-1.5 rounded-full bg-amber-500"></span>Open <span class="tabular-nums opacity-60">1</span></label>
        <label class="inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-full border border-tsp04-line bg-white px-4 text-sm font-semibold text-tsp04-mut shadow-xs transition hover:text-tsp04-ink has-checked:border-transparent has-checked:bg-tsp04-ink has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-tsp04-acc"><input type="radio" name="tsp-04-f" id="tsp-04-f-failed" class="sr-only"><span class="size-1.5 rounded-full bg-rose-500"></span>Failed <span class="tabular-nums opacity-60">1</span></label>
        <label class="inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-full border border-tsp04-line bg-white px-4 text-sm font-semibold text-tsp04-mut shadow-xs transition hover:text-tsp04-ink has-checked:border-transparent has-checked:bg-tsp04-ink has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-tsp04-acc"><input type="radio" name="tsp-04-f" id="tsp-04-f-refunded" class="sr-only"><span class="size-1.5 rounded-full bg-zinc-400"></span>Refunded <span class="tabular-nums opacity-60">1</span></label>
      </fieldset>
      <div class="hidden items-center gap-3 rounded-full bg-tsp04-acc/10 py-1 pl-4 pr-1 text-sm font-semibold text-tsp04-acc group-has-[[data-row-check]:checked]/inv:flex" aria-live="polite"><span data-sel-count>0 selected</span><button type="button" data-bulk class="inline-flex min-h-10 items-center gap-2 rounded-full bg-tsp04-acc px-4 text-white transition hover:bg-tsp04-acc/90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tsp04-acc">Download selected</button></div>
    </div>

    <div class="overflow-x-auto rounded-3xl border border-tsp04-line bg-white shadow-[0_1px_2px_rgb(0_0_0/0.04)]">
      <table class="w-full min-w-[46rem] border-collapse text-left text-sm">
        <thead class="border-b border-tsp04-line bg-tsp04-bg/60 text-xs font-semibold uppercase tracking-wide text-tsp04-mut">
          <tr><th scope="col" class="w-14 pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-all class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select all visible invoices"></label></th><th scope="col" class="py-3 pr-4">Invoice</th><th scope="col" class="py-3 pr-4">Date</th><th scope="col" class="py-3 pr-4">Description</th><th scope="col" class="py-3 pr-4">Status</th><th scope="col" class="py-3 pr-4 text-right">Amount</th><th scope="col" class="py-3 pr-5 text-right"><span class="sr-only">Download</span></th></tr>
        </thead>
        <tbody class="divide-y divide-tsp04-line group-has-[#tsp-04-f-paid:checked]/inv:[&>tr:not([data-status=paid])]:hidden group-has-[#tsp-04-f-open:checked]/inv:[&>tr:not([data-status=open])]:hidden group-has-[#tsp-04-f-failed:checked]/inv:[&>tr:not([data-status=failed])]:hidden group-has-[#tsp-04-f-refunded:checked]/inv:[&>tr:not([data-status=refunded])]:hidden">
          <tr data-status="open" data-id="INV-2026-011" data-amount="$288.00" class="transition hover:bg-tsp04-bg/60 has-checked:bg-tsp04-acc/5"><td class="pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-row-check class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select INV-2026-011"></label></td><th scope="row" class="py-3 pr-4 font-tsp04-mono font-medium">INV-2026-011</th><td class="py-3 pr-4 text-tsp04-mut">Due Oct 30, 2026</td><td class="py-3 pr-4">Pro · 12 seats · monthly</td><td class="py-3 pr-4"><span class="inline-flex items-center gap-1.5 rounded-full bg-amber-50 px-2.5 py-1 text-xs font-semibold text-amber-700 ring-1 ring-amber-600/15"><span class="size-1.5 rounded-full bg-amber-500"></span>Open</span></td><td class="py-3 pr-4 text-right font-semibold tabular-nums">$288.00</td><td class="py-2 pr-3 text-right"><button type="button" data-dl data-state="idle" class="group/dl ml-auto grid size-11 place-items-center rounded-xl text-tsp04-mut transition hover:bg-tsp04-bg hover:text-tsp04-ink focus-visible:outline-2 focus-visible:outline-tsp04-acc data-[state=done]:text-emerald-600" aria-label="Download INV-2026-011"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 group-data-[state=loading]/dl:hidden group-data-[state=done]/dl:hidden"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" class="hidden size-4 animate-spin group-data-[state=loading]/dl:block"><path d="M12 3a9 9 0 1 0 9 9"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="hidden size-4 group-data-[state=done]/dl:block"><path d="M20 6 9 17l-5-5"/></svg></button></td></tr>
          <tr data-status="paid" data-id="INV-2026-010" data-amount="$288.00" class="transition hover:bg-tsp04-bg/60 has-checked:bg-tsp04-acc/5"><td class="pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-row-check class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select INV-2026-010"></label></td><th scope="row" class="py-3 pr-4 font-tsp04-mono font-medium">INV-2026-010</th><td class="py-3 pr-4 text-tsp04-mut">Oct 1, 2026</td><td class="py-3 pr-4">Pro · 12 seats · monthly</td><td class="py-3 pr-4"><span class="inline-flex items-center gap-1.5 rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-semibold text-emerald-700 ring-1 ring-emerald-600/15"><span class="size-1.5 rounded-full bg-emerald-500"></span>Paid</span></td><td class="py-3 pr-4 text-right font-semibold tabular-nums">$288.00</td><td class="py-2 pr-3 text-right"><button type="button" data-dl data-state="idle" class="group/dl ml-auto grid size-11 place-items-center rounded-xl text-tsp04-mut transition hover:bg-tsp04-bg hover:text-tsp04-ink focus-visible:outline-2 focus-visible:outline-tsp04-acc data-[state=done]:text-emerald-600" aria-label="Download INV-2026-010"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 group-data-[state=loading]/dl:hidden group-data-[state=done]/dl:hidden"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" class="hidden size-4 animate-spin group-data-[state=loading]/dl:block"><path d="M12 3a9 9 0 1 0 9 9"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="hidden size-4 group-data-[state=done]/dl:block"><path d="M20 6 9 17l-5-5"/></svg></button></td></tr>
          <tr data-status="paid" data-id="INV-2026-009" data-amount="$288.00" class="transition hover:bg-tsp04-bg/60 has-checked:bg-tsp04-acc/5"><td class="pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-row-check class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select INV-2026-009"></label></td><th scope="row" class="py-3 pr-4 font-tsp04-mono font-medium">INV-2026-009</th><td class="py-3 pr-4 text-tsp04-mut">Sep 1, 2026</td><td class="py-3 pr-4">Pro · 12 seats · monthly</td><td class="py-3 pr-4"><span class="inline-flex items-center gap-1.5 rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-semibold text-emerald-700 ring-1 ring-emerald-600/15"><span class="size-1.5 rounded-full bg-emerald-500"></span>Paid</span></td><td class="py-3 pr-4 text-right font-semibold tabular-nums">$288.00</td><td class="py-2 pr-3 text-right"><button type="button" data-dl data-state="idle" class="group/dl ml-auto grid size-11 place-items-center rounded-xl text-tsp04-mut transition hover:bg-tsp04-bg hover:text-tsp04-ink focus-visible:outline-2 focus-visible:outline-tsp04-acc data-[state=done]:text-emerald-600" aria-label="Download INV-2026-009"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 group-data-[state=loading]/dl:hidden group-data-[state=done]/dl:hidden"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" class="hidden size-4 animate-spin group-data-[state=loading]/dl:block"><path d="M12 3a9 9 0 1 0 9 9"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="hidden size-4 group-data-[state=done]/dl:block"><path d="M20 6 9 17l-5-5"/></svg></button></td></tr>
          <tr data-status="failed" data-id="INV-2026-008" data-amount="$264.00" class="transition hover:bg-tsp04-bg/60 has-checked:bg-tsp04-acc/5"><td class="pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-row-check class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select INV-2026-008"></label></td><th scope="row" class="py-3 pr-4 font-tsp04-mono font-medium">INV-2026-008</th><td class="py-3 pr-4 text-tsp04-mut">Aug 1, 2026</td><td class="py-3 pr-4">Pro · 11 seats · monthly</td><td class="py-3 pr-4"><span class="inline-flex items-center gap-1.5 rounded-full bg-rose-50 px-2.5 py-1 text-xs font-semibold text-rose-700 ring-1 ring-rose-600/15"><span class="size-1.5 rounded-full bg-rose-500"></span>Failed</span> <a href="#tsp-04-h" class="ml-1 text-xs font-semibold text-tsp04-acc hover:underline">Retry</a></td><td class="py-3 pr-4 text-right font-semibold tabular-nums">$264.00</td><td class="py-2 pr-3 text-right"><button type="button" data-dl data-state="idle" class="group/dl ml-auto grid size-11 place-items-center rounded-xl text-tsp04-mut transition hover:bg-tsp04-bg hover:text-tsp04-ink focus-visible:outline-2 focus-visible:outline-tsp04-acc data-[state=done]:text-emerald-600" aria-label="Download INV-2026-008"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 group-data-[state=loading]/dl:hidden group-data-[state=done]/dl:hidden"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" class="hidden size-4 animate-spin group-data-[state=loading]/dl:block"><path d="M12 3a9 9 0 1 0 9 9"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="hidden size-4 group-data-[state=done]/dl:block"><path d="M20 6 9 17l-5-5"/></svg></button></td></tr>
          <tr data-status="paid" data-id="INV-2026-007" data-amount="$264.00" class="transition hover:bg-tsp04-bg/60 has-checked:bg-tsp04-acc/5"><td class="pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-row-check class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select INV-2026-007"></label></td><th scope="row" class="py-3 pr-4 font-tsp04-mono font-medium">INV-2026-007</th><td class="py-3 pr-4 text-tsp04-mut">Jul 1, 2026</td><td class="py-3 pr-4">Pro · 11 seats · monthly</td><td class="py-3 pr-4"><span class="inline-flex items-center gap-1.5 rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-semibold text-emerald-700 ring-1 ring-emerald-600/15"><span class="size-1.5 rounded-full bg-emerald-500"></span>Paid</span></td><td class="py-3 pr-4 text-right font-semibold tabular-nums">$264.00</td><td class="py-2 pr-3 text-right"><button type="button" data-dl data-state="idle" class="group/dl ml-auto grid size-11 place-items-center rounded-xl text-tsp04-mut transition hover:bg-tsp04-bg hover:text-tsp04-ink focus-visible:outline-2 focus-visible:outline-tsp04-acc data-[state=done]:text-emerald-600" aria-label="Download INV-2026-007"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 group-data-[state=loading]/dl:hidden group-data-[state=done]/dl:hidden"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" class="hidden size-4 animate-spin group-data-[state=loading]/dl:block"><path d="M12 3a9 9 0 1 0 9 9"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="hidden size-4 group-data-[state=done]/dl:block"><path d="M20 6 9 17l-5-5"/></svg></button></td></tr>
          <tr data-status="refunded" data-id="INV-2026-006" data-amount="$40.00" class="transition hover:bg-tsp04-bg/60 has-checked:bg-tsp04-acc/5"><td class="pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-row-check class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select INV-2026-006"></label></td><th scope="row" class="py-3 pr-4 font-tsp04-mono font-medium">INV-2026-006</th><td class="py-3 pr-4 text-tsp04-mut">Jun 15, 2026</td><td class="py-3 pr-4">Add-on · 10k AI credits</td><td class="py-3 pr-4"><span class="inline-flex items-center gap-1.5 rounded-full bg-zinc-100 px-2.5 py-1 text-xs font-semibold text-zinc-600 ring-1 ring-zinc-500/15"><span class="size-1.5 rounded-full bg-zinc-400"></span>Refunded</span></td><td class="py-3 pr-4 text-right font-semibold tabular-nums text-tsp04-mut line-through decoration-zinc-400">$40.00</td><td class="py-2 pr-3 text-right"><button type="button" data-dl data-state="idle" class="group/dl ml-auto grid size-11 place-items-center rounded-xl text-tsp04-mut transition hover:bg-tsp04-bg hover:text-tsp04-ink focus-visible:outline-2 focus-visible:outline-tsp04-acc data-[state=done]:text-emerald-600" aria-label="Download INV-2026-006"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 group-data-[state=loading]/dl:hidden group-data-[state=done]/dl:hidden"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" class="hidden size-4 animate-spin group-data-[state=loading]/dl:block"><path d="M12 3a9 9 0 1 0 9 9"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="hidden size-4 group-data-[state=done]/dl:block"><path d="M20 6 9 17l-5-5"/></svg></button></td></tr>
          <tr data-status="paid" data-id="INV-2026-005" data-amount="$240.00" class="transition hover:bg-tsp04-bg/60 has-checked:bg-tsp04-acc/5"><td class="pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-row-check class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select INV-2026-005"></label></td><th scope="row" class="py-3 pr-4 font-tsp04-mono font-medium">INV-2026-005</th><td class="py-3 pr-4 text-tsp04-mut">Jun 1, 2026</td><td class="py-3 pr-4">Pro · 10 seats · monthly</td><td class="py-3 pr-4"><span class="inline-flex items-center gap-1.5 rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-semibold text-emerald-700 ring-1 ring-emerald-600/15"><span class="size-1.5 rounded-full bg-emerald-500"></span>Paid</span></td><td class="py-3 pr-4 text-right font-semibold tabular-nums">$240.00</td><td class="py-2 pr-3 text-right"><button type="button" data-dl data-state="idle" class="group/dl ml-auto grid size-11 place-items-center rounded-xl text-tsp04-mut transition hover:bg-tsp04-bg hover:text-tsp04-ink focus-visible:outline-2 focus-visible:outline-tsp04-acc data-[state=done]:text-emerald-600" aria-label="Download INV-2026-005"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 group-data-[state=loading]/dl:hidden group-data-[state=done]/dl:hidden"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" class="hidden size-4 animate-spin group-data-[state=loading]/dl:block"><path d="M12 3a9 9 0 1 0 9 9"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="hidden size-4 group-data-[state=done]/dl:block"><path d="M20 6 9 17l-5-5"/></svg></button></td></tr>
          <tr data-status="paid" data-id="INV-2026-004" data-amount="$240.00" class="transition hover:bg-tsp04-bg/60 has-checked:bg-tsp04-acc/5"><td class="pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-row-check class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select INV-2026-004"></label></td><th scope="row" class="py-3 pr-4 font-tsp04-mono font-medium">INV-2026-004</th><td class="py-3 pr-4 text-tsp04-mut">May 1, 2026</td><td class="py-3 pr-4">Pro · 10 seats · monthly</td><td class="py-3 pr-4"><span class="inline-flex items-center gap-1.5 rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-semibold text-emerald-700 ring-1 ring-emerald-600/15"><span class="size-1.5 rounded-full bg-emerald-500"></span>Paid</span></td><td class="py-3 pr-4 text-right font-semibold tabular-nums">$240.00</td><td class="py-2 pr-3 text-right"><button type="button" data-dl data-state="idle" class="group/dl ml-auto grid size-11 place-items-center rounded-xl text-tsp04-mut transition hover:bg-tsp04-bg hover:text-tsp04-ink focus-visible:outline-2 focus-visible:outline-tsp04-acc data-[state=done]:text-emerald-600" aria-label="Download INV-2026-004"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 group-data-[state=loading]/dl:hidden group-data-[state=done]/dl:hidden"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" class="hidden size-4 animate-spin group-data-[state=loading]/dl:block"><path d="M12 3a9 9 0 1 0 9 9"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="hidden size-4 group-data-[state=done]/dl:block"><path d="M20 6 9 17l-5-5"/></svg></button></td></tr>
        </tbody>
      </table>
    </div>
    <p class="text-sm text-tsp04-mut">Need a VAT invoice or a different billing address? Update your <a href="#tsp-04-h" class="font-semibold text-tsp04-acc underline-offset-4 hover:underline">billing details</a> — future invoices follow.</p>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tsp04-bg: #f6f6f8;
  --color-tsp04-bg: oklch(0.974 0.003 286);
  --color-tsp04-ink: #17171c;
  --color-tsp04-ink: oklch(0.205 0.008 286);
  --color-tsp04-mut: #61616c;
  --color-tsp04-mut: oklch(0.5 0.015 286);
  --color-tsp04-line: #e7e7ec;
  --color-tsp04-line: oklch(0.925 0.006 286);
  --color-tsp04-acc: #4f46e5;
  --color-tsp04-acc: oklch(0.511 0.23 277);
  --font-tsp04-sans: 'DM Sans', system-ui, sans-serif;
  --font-tsp04-mono: 'DM Mono', ui-monospace, monospace;
}

.tsp-04 {
  width: 100%;
  min-height: 100svh;
}
(() => {
  const root = document.querySelector('[data-tsp04]');
  if (!root || root.dataset.wired) return;
  root.dataset.wired = '1';
  const all = root.querySelector('[data-all]');
  const count = root.querySelector('[data-sel-count]');
  const rows = () => [...root.querySelectorAll('tbody tr')].filter((r) => r.offsetParent !== null);
  const sync = () => {
    const vis = rows();
    const on = vis.filter((r) => r.querySelector('[data-row-check]').checked).length;
    all.checked = vis.length > 0 && on === vis.length;
    all.indeterminate = on > 0 && on < vis.length;
    const total = root.querySelectorAll('[data-row-check]:checked').length;
    count.textContent = total + ' selected';
  };
  all.addEventListener('change', () => { rows().forEach((r) => { r.querySelector('[data-row-check]').checked = all.checked; }); sync(); });
  root.addEventListener('change', (e) => { if (e.target.matches('[data-row-check], [name=tsp-04-f]')) setTimeout(sync); });
  const save = (row) => {
    const lines = ['NORTHWIND CO — INVOICE', '', 'Invoice: ' + row.dataset.id, 'Status:  ' + row.dataset.status, 'Amount:  ' + row.dataset.amount, '', 'Thank you for your business.'];
    const url = URL.createObjectURL(new Blob([lines.join('\n')], { type: 'text/plain' }));
    const a = document.createElement('a');
    a.href = url; a.download = row.dataset.id + '.txt';
    document.body.appendChild(a); a.click(); a.remove();
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  };
  const run = (btn) => {
    if (btn.dataset.state === 'loading') return;
    btn.dataset.state = 'loading';
    setTimeout(() => {
      save(btn.closest('tr'));
      btn.dataset.state = 'done';
      setTimeout(() => { btn.dataset.state = 'idle'; }, 1800);
    }, 900);
  };
  root.querySelectorAll('[data-dl]').forEach((b) => b.addEventListener('click', () => run(b)));
  root.querySelector('[data-bulk]').addEventListener('click', () => {
    root.querySelectorAll('[data-row-check]:checked').forEach((c, i) => setTimeout(() => run(c.closest('tr').querySelector('[data-dl]')), i * 250));
  });
  sync();
})();

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

Compiles in the browser — perfect for a CodePen or a quick test, not for production.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-tsp04-bg: #f6f6f8;
  --color-tsp04-bg: oklch(0.974 0.003 286);
  --color-tsp04-ink: #17171c;
  --color-tsp04-ink: oklch(0.205 0.008 286);
  --color-tsp04-mut: #61616c;
  --color-tsp04-mut: oklch(0.5 0.015 286);
  --color-tsp04-line: #e7e7ec;
  --color-tsp04-line: oklch(0.925 0.006 286);
  --color-tsp04-acc: #4f46e5;
  --color-tsp04-acc: oklch(0.511 0.23 277);
  --font-tsp04-sans: 'DM Sans', system-ui, sans-serif;
  --font-tsp04-mono: 'DM Mono', ui-monospace, monospace;
}

.tsp-04 {
  width: 100%;
  min-height: 100svh;
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,400..700&family=DM+Mono:wght@400;500&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • The CSS original's .csp-04:has(#paid:checked) tbody tr:not([data-status=paid]) rules became four stacked variants on the <tbody> — the filter lives where it acts.
  • The CSS-counter 'N selected' label moved into the script, which also had to own indeterminate; the bulk bar's visibility stayed CSS via group-has-[[data-row-check]:checked]/inv:flex.
  • UI upgrade from the CSS original: a failed-payment alert with a direct fix, a Retry link on the failed row and struck-through refunded amounts.
  • UI upgrade: 'Download selected' staggers real downloads through each row's own idle → spinner → check button.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Invoice History Table
Source: https://codefronts.com/layouts/tailwind-css-settings-pages/tailwind-invoice-history-table/

A billing-history table with zero-JS status filter chips, row selection that tints the row and raises a bulk-action bar, a native indeterminate select-all, status pills for Paid, Open, Failed and Refunded, and per-row download buttons that cycle idle → generating → done before handing over a real file.
## HTML
```html
<section data-tsp04 class="tsp-04 group/inv w-full min-h-svh bg-tsp04-bg px-4 py-10 font-tsp04-sans text-tsp04-ink antialiased sm:px-8 sm:py-14" aria-labelledby="tsp-04-h">
  <div class="mx-auto grid w-full max-w-6xl gap-6">
    <header class="flex flex-wrap items-end justify-between gap-4">
      <div><h1 id="tsp-04-h" class="text-3xl font-bold tracking-tight">Invoices</h1><p class="mt-1 text-tsp04-mut">Sent to <strong class="font-semibold text-tsp04-ink">billing@northwind.co</strong> · <a href="#tsp-04-h" class="font-semibold text-tsp04-acc underline-offset-4 hover:underline focus-visible:outline-2 focus-visible:outline-tsp04-acc">Change</a></p></div>
      <label class="grid gap-1 text-sm"><span class="sr-only">Year</span><select class="min-h-11 rounded-xl border border-tsp04-line bg-white px-3 text-sm font-medium shadow-xs outline-none focus:ring-4 focus:ring-tsp04-acc/15"><option>2026</option><option>2025</option><option>2024</option></select></label>
    </header>

    <div role="alert" class="flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-800">
      <p class="flex items-center gap-2"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 flex-none"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg><span><strong class="font-semibold">INV-2026-008 failed.</strong> Visa ending 4242 was declined on Aug 1.</span></p>
      <a href="#tsp-04-h" class="inline-flex min-h-11 items-center rounded-xl bg-rose-600 px-4 font-semibold text-white transition hover:bg-rose-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-rose-600">Update payment method</a>
    </div>

    <div class="flex flex-wrap items-center justify-between gap-3">
      <fieldset class="flex flex-wrap gap-2">
        <legend class="sr-only">Filter by status</legend>
        <label class="inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-full border border-tsp04-line bg-white px-4 text-sm font-semibold text-tsp04-mut shadow-xs transition hover:text-tsp04-ink has-checked:border-transparent has-checked:bg-tsp04-ink has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-tsp04-acc"><input type="radio" name="tsp-04-f" id="tsp-04-f-all" class="sr-only" checked>All <span class="tabular-nums opacity-60">8</span></label>
        <label class="inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-full border border-tsp04-line bg-white px-4 text-sm font-semibold text-tsp04-mut shadow-xs transition hover:text-tsp04-ink has-checked:border-transparent has-checked:bg-tsp04-ink has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-tsp04-acc"><input type="radio" name="tsp-04-f" id="tsp-04-f-paid" class="sr-only"><span class="size-1.5 rounded-full bg-emerald-500"></span>Paid <span class="tabular-nums opacity-60">5</span></label>
        <label class="inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-full border border-tsp04-line bg-white px-4 text-sm font-semibold text-tsp04-mut shadow-xs transition hover:text-tsp04-ink has-checked:border-transparent has-checked:bg-tsp04-ink has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-tsp04-acc"><input type="radio" name="tsp-04-f" id="tsp-04-f-open" class="sr-only"><span class="size-1.5 rounded-full bg-amber-500"></span>Open <span class="tabular-nums opacity-60">1</span></label>
        <label class="inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-full border border-tsp04-line bg-white px-4 text-sm font-semibold text-tsp04-mut shadow-xs transition hover:text-tsp04-ink has-checked:border-transparent has-checked:bg-tsp04-ink has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-tsp04-acc"><input type="radio" name="tsp-04-f" id="tsp-04-f-failed" class="sr-only"><span class="size-1.5 rounded-full bg-rose-500"></span>Failed <span class="tabular-nums opacity-60">1</span></label>
        <label class="inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-full border border-tsp04-line bg-white px-4 text-sm font-semibold text-tsp04-mut shadow-xs transition hover:text-tsp04-ink has-checked:border-transparent has-checked:bg-tsp04-ink has-checked:text-white has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-tsp04-acc"><input type="radio" name="tsp-04-f" id="tsp-04-f-refunded" class="sr-only"><span class="size-1.5 rounded-full bg-zinc-400"></span>Refunded <span class="tabular-nums opacity-60">1</span></label>
      </fieldset>
      <div class="hidden items-center gap-3 rounded-full bg-tsp04-acc/10 py-1 pl-4 pr-1 text-sm font-semibold text-tsp04-acc group-has-[[data-row-check]:checked]/inv:flex" aria-live="polite"><span data-sel-count>0 selected</span><button type="button" data-bulk class="inline-flex min-h-10 items-center gap-2 rounded-full bg-tsp04-acc px-4 text-white transition hover:bg-tsp04-acc/90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tsp04-acc">Download selected</button></div>
    </div>

    <div class="overflow-x-auto rounded-3xl border border-tsp04-line bg-white shadow-[0_1px_2px_rgb(0_0_0/0.04)]">
      <table class="w-full min-w-[46rem] border-collapse text-left text-sm">
        <thead class="border-b border-tsp04-line bg-tsp04-bg/60 text-xs font-semibold uppercase tracking-wide text-tsp04-mut">
          <tr><th scope="col" class="w-14 pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-all class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select all visible invoices"></label></th><th scope="col" class="py-3 pr-4">Invoice</th><th scope="col" class="py-3 pr-4">Date</th><th scope="col" class="py-3 pr-4">Description</th><th scope="col" class="py-3 pr-4">Status</th><th scope="col" class="py-3 pr-4 text-right">Amount</th><th scope="col" class="py-3 pr-5 text-right"><span class="sr-only">Download</span></th></tr>
        </thead>
        <tbody class="divide-y divide-tsp04-line group-has-[#tsp-04-f-paid:checked]/inv:[&>tr:not([data-status=paid])]:hidden group-has-[#tsp-04-f-open:checked]/inv:[&>tr:not([data-status=open])]:hidden group-has-[#tsp-04-f-failed:checked]/inv:[&>tr:not([data-status=failed])]:hidden group-has-[#tsp-04-f-refunded:checked]/inv:[&>tr:not([data-status=refunded])]:hidden">
          <tr data-status="open" data-id="INV-2026-011" data-amount="$288.00" class="transition hover:bg-tsp04-bg/60 has-checked:bg-tsp04-acc/5"><td class="pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-row-check class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select INV-2026-011"></label></td><th scope="row" class="py-3 pr-4 font-tsp04-mono font-medium">INV-2026-011</th><td class="py-3 pr-4 text-tsp04-mut">Due Oct 30, 2026</td><td class="py-3 pr-4">Pro · 12 seats · monthly</td><td class="py-3 pr-4"><span class="inline-flex items-center gap-1.5 rounded-full bg-amber-50 px-2.5 py-1 text-xs font-semibold text-amber-700 ring-1 ring-amber-600/15"><span class="size-1.5 rounded-full bg-amber-500"></span>Open</span></td><td class="py-3 pr-4 text-right font-semibold tabular-nums">$288.00</td><td class="py-2 pr-3 text-right"><button type="button" data-dl data-state="idle" class="group/dl ml-auto grid size-11 place-items-center rounded-xl text-tsp04-mut transition hover:bg-tsp04-bg hover:text-tsp04-ink focus-visible:outline-2 focus-visible:outline-tsp04-acc data-[state=done]:text-emerald-600" aria-label="Download INV-2026-011"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 group-data-[state=loading]/dl:hidden group-data-[state=done]/dl:hidden"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" class="hidden size-4 animate-spin group-data-[state=loading]/dl:block"><path d="M12 3a9 9 0 1 0 9 9"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="hidden size-4 group-data-[state=done]/dl:block"><path d="M20 6 9 17l-5-5"/></svg></button></td></tr>
          <tr data-status="paid" data-id="INV-2026-010" data-amount="$288.00" class="transition hover:bg-tsp04-bg/60 has-checked:bg-tsp04-acc/5"><td class="pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-row-check class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select INV-2026-010"></label></td><th scope="row" class="py-3 pr-4 font-tsp04-mono font-medium">INV-2026-010</th><td class="py-3 pr-4 text-tsp04-mut">Oct 1, 2026</td><td class="py-3 pr-4">Pro · 12 seats · monthly</td><td class="py-3 pr-4"><span class="inline-flex items-center gap-1.5 rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-semibold text-emerald-700 ring-1 ring-emerald-600/15"><span class="size-1.5 rounded-full bg-emerald-500"></span>Paid</span></td><td class="py-3 pr-4 text-right font-semibold tabular-nums">$288.00</td><td class="py-2 pr-3 text-right"><button type="button" data-dl data-state="idle" class="group/dl ml-auto grid size-11 place-items-center rounded-xl text-tsp04-mut transition hover:bg-tsp04-bg hover:text-tsp04-ink focus-visible:outline-2 focus-visible:outline-tsp04-acc data-[state=done]:text-emerald-600" aria-label="Download INV-2026-010"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 group-data-[state=loading]/dl:hidden group-data-[state=done]/dl:hidden"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" class="hidden size-4 animate-spin group-data-[state=loading]/dl:block"><path d="M12 3a9 9 0 1 0 9 9"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="hidden size-4 group-data-[state=done]/dl:block"><path d="M20 6 9 17l-5-5"/></svg></button></td></tr>
          <tr data-status="paid" data-id="INV-2026-009" data-amount="$288.00" class="transition hover:bg-tsp04-bg/60 has-checked:bg-tsp04-acc/5"><td class="pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-row-check class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select INV-2026-009"></label></td><th scope="row" class="py-3 pr-4 font-tsp04-mono font-medium">INV-2026-009</th><td class="py-3 pr-4 text-tsp04-mut">Sep 1, 2026</td><td class="py-3 pr-4">Pro · 12 seats · monthly</td><td class="py-3 pr-4"><span class="inline-flex items-center gap-1.5 rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-semibold text-emerald-700 ring-1 ring-emerald-600/15"><span class="size-1.5 rounded-full bg-emerald-500"></span>Paid</span></td><td class="py-3 pr-4 text-right font-semibold tabular-nums">$288.00</td><td class="py-2 pr-3 text-right"><button type="button" data-dl data-state="idle" class="group/dl ml-auto grid size-11 place-items-center rounded-xl text-tsp04-mut transition hover:bg-tsp04-bg hover:text-tsp04-ink focus-visible:outline-2 focus-visible:outline-tsp04-acc data-[state=done]:text-emerald-600" aria-label="Download INV-2026-009"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 group-data-[state=loading]/dl:hidden group-data-[state=done]/dl:hidden"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" class="hidden size-4 animate-spin group-data-[state=loading]/dl:block"><path d="M12 3a9 9 0 1 0 9 9"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="hidden size-4 group-data-[state=done]/dl:block"><path d="M20 6 9 17l-5-5"/></svg></button></td></tr>
          <tr data-status="failed" data-id="INV-2026-008" data-amount="$264.00" class="transition hover:bg-tsp04-bg/60 has-checked:bg-tsp04-acc/5"><td class="pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-row-check class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select INV-2026-008"></label></td><th scope="row" class="py-3 pr-4 font-tsp04-mono font-medium">INV-2026-008</th><td class="py-3 pr-4 text-tsp04-mut">Aug 1, 2026</td><td class="py-3 pr-4">Pro · 11 seats · monthly</td><td class="py-3 pr-4"><span class="inline-flex items-center gap-1.5 rounded-full bg-rose-50 px-2.5 py-1 text-xs font-semibold text-rose-700 ring-1 ring-rose-600/15"><span class="size-1.5 rounded-full bg-rose-500"></span>Failed</span> <a href="#tsp-04-h" class="ml-1 text-xs font-semibold text-tsp04-acc hover:underline">Retry</a></td><td class="py-3 pr-4 text-right font-semibold tabular-nums">$264.00</td><td class="py-2 pr-3 text-right"><button type="button" data-dl data-state="idle" class="group/dl ml-auto grid size-11 place-items-center rounded-xl text-tsp04-mut transition hover:bg-tsp04-bg hover:text-tsp04-ink focus-visible:outline-2 focus-visible:outline-tsp04-acc data-[state=done]:text-emerald-600" aria-label="Download INV-2026-008"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 group-data-[state=loading]/dl:hidden group-data-[state=done]/dl:hidden"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" class="hidden size-4 animate-spin group-data-[state=loading]/dl:block"><path d="M12 3a9 9 0 1 0 9 9"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="hidden size-4 group-data-[state=done]/dl:block"><path d="M20 6 9 17l-5-5"/></svg></button></td></tr>
          <tr data-status="paid" data-id="INV-2026-007" data-amount="$264.00" class="transition hover:bg-tsp04-bg/60 has-checked:bg-tsp04-acc/5"><td class="pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-row-check class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select INV-2026-007"></label></td><th scope="row" class="py-3 pr-4 font-tsp04-mono font-medium">INV-2026-007</th><td class="py-3 pr-4 text-tsp04-mut">Jul 1, 2026</td><td class="py-3 pr-4">Pro · 11 seats · monthly</td><td class="py-3 pr-4"><span class="inline-flex items-center gap-1.5 rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-semibold text-emerald-700 ring-1 ring-emerald-600/15"><span class="size-1.5 rounded-full bg-emerald-500"></span>Paid</span></td><td class="py-3 pr-4 text-right font-semibold tabular-nums">$264.00</td><td class="py-2 pr-3 text-right"><button type="button" data-dl data-state="idle" class="group/dl ml-auto grid size-11 place-items-center rounded-xl text-tsp04-mut transition hover:bg-tsp04-bg hover:text-tsp04-ink focus-visible:outline-2 focus-visible:outline-tsp04-acc data-[state=done]:text-emerald-600" aria-label="Download INV-2026-007"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 group-data-[state=loading]/dl:hidden group-data-[state=done]/dl:hidden"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" class="hidden size-4 animate-spin group-data-[state=loading]/dl:block"><path d="M12 3a9 9 0 1 0 9 9"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="hidden size-4 group-data-[state=done]/dl:block"><path d="M20 6 9 17l-5-5"/></svg></button></td></tr>
          <tr data-status="refunded" data-id="INV-2026-006" data-amount="$40.00" class="transition hover:bg-tsp04-bg/60 has-checked:bg-tsp04-acc/5"><td class="pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-row-check class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select INV-2026-006"></label></td><th scope="row" class="py-3 pr-4 font-tsp04-mono font-medium">INV-2026-006</th><td class="py-3 pr-4 text-tsp04-mut">Jun 15, 2026</td><td class="py-3 pr-4">Add-on · 10k AI credits</td><td class="py-3 pr-4"><span class="inline-flex items-center gap-1.5 rounded-full bg-zinc-100 px-2.5 py-1 text-xs font-semibold text-zinc-600 ring-1 ring-zinc-500/15"><span class="size-1.5 rounded-full bg-zinc-400"></span>Refunded</span></td><td class="py-3 pr-4 text-right font-semibold tabular-nums text-tsp04-mut line-through decoration-zinc-400">$40.00</td><td class="py-2 pr-3 text-right"><button type="button" data-dl data-state="idle" class="group/dl ml-auto grid size-11 place-items-center rounded-xl text-tsp04-mut transition hover:bg-tsp04-bg hover:text-tsp04-ink focus-visible:outline-2 focus-visible:outline-tsp04-acc data-[state=done]:text-emerald-600" aria-label="Download INV-2026-006"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 group-data-[state=loading]/dl:hidden group-data-[state=done]/dl:hidden"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" class="hidden size-4 animate-spin group-data-[state=loading]/dl:block"><path d="M12 3a9 9 0 1 0 9 9"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="hidden size-4 group-data-[state=done]/dl:block"><path d="M20 6 9 17l-5-5"/></svg></button></td></tr>
          <tr data-status="paid" data-id="INV-2026-005" data-amount="$240.00" class="transition hover:bg-tsp04-bg/60 has-checked:bg-tsp04-acc/5"><td class="pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-row-check class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select INV-2026-005"></label></td><th scope="row" class="py-3 pr-4 font-tsp04-mono font-medium">INV-2026-005</th><td class="py-3 pr-4 text-tsp04-mut">Jun 1, 2026</td><td class="py-3 pr-4">Pro · 10 seats · monthly</td><td class="py-3 pr-4"><span class="inline-flex items-center gap-1.5 rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-semibold text-emerald-700 ring-1 ring-emerald-600/15"><span class="size-1.5 rounded-full bg-emerald-500"></span>Paid</span></td><td class="py-3 pr-4 text-right font-semibold tabular-nums">$240.00</td><td class="py-2 pr-3 text-right"><button type="button" data-dl data-state="idle" class="group/dl ml-auto grid size-11 place-items-center rounded-xl text-tsp04-mut transition hover:bg-tsp04-bg hover:text-tsp04-ink focus-visible:outline-2 focus-visible:outline-tsp04-acc data-[state=done]:text-emerald-600" aria-label="Download INV-2026-005"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 group-data-[state=loading]/dl:hidden group-data-[state=done]/dl:hidden"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" class="hidden size-4 animate-spin group-data-[state=loading]/dl:block"><path d="M12 3a9 9 0 1 0 9 9"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="hidden size-4 group-data-[state=done]/dl:block"><path d="M20 6 9 17l-5-5"/></svg></button></td></tr>
          <tr data-status="paid" data-id="INV-2026-004" data-amount="$240.00" class="transition hover:bg-tsp04-bg/60 has-checked:bg-tsp04-acc/5"><td class="pl-3"><label class="grid size-11 cursor-pointer place-items-center"><input type="checkbox" data-row-check class="size-4 cursor-pointer accent-tsp04-acc" aria-label="Select INV-2026-004"></label></td><th scope="row" class="py-3 pr-4 font-tsp04-mono font-medium">INV-2026-004</th><td class="py-3 pr-4 text-tsp04-mut">May 1, 2026</td><td class="py-3 pr-4">Pro · 10 seats · monthly</td><td class="py-3 pr-4"><span class="inline-flex items-center gap-1.5 rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-semibold text-emerald-700 ring-1 ring-emerald-600/15"><span class="size-1.5 rounded-full bg-emerald-500"></span>Paid</span></td><td class="py-3 pr-4 text-right font-semibold tabular-nums">$240.00</td><td class="py-2 pr-3 text-right"><button type="button" data-dl data-state="idle" class="group/dl ml-auto grid size-11 place-items-center rounded-xl text-tsp04-mut transition hover:bg-tsp04-bg hover:text-tsp04-ink focus-visible:outline-2 focus-visible:outline-tsp04-acc data-[state=done]:text-emerald-600" aria-label="Download INV-2026-004"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 group-data-[state=loading]/dl:hidden group-data-[state=done]/dl:hidden"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" class="hidden size-4 animate-spin group-data-[state=loading]/dl:block"><path d="M12 3a9 9 0 1 0 9 9"/></svg><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="hidden size-4 group-data-[state=done]/dl:block"><path d="M20 6 9 17l-5-5"/></svg></button></td></tr>
        </tbody>
      </table>
    </div>
    <p class="text-sm text-tsp04-mut">Need a VAT invoice or a different billing address? Update your <a href="#tsp-04-h" class="font-semibold text-tsp04-acc underline-offset-4 hover:underline">billing details</a> — future invoices follow.</p>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tsp04-bg: #f6f6f8;
  --color-tsp04-bg: oklch(0.974 0.003 286);
  --color-tsp04-ink: #17171c;
  --color-tsp04-ink: oklch(0.205 0.008 286);
  --color-tsp04-mut: #61616c;
  --color-tsp04-mut: oklch(0.5 0.015 286);
  --color-tsp04-line: #e7e7ec;
  --color-tsp04-line: oklch(0.925 0.006 286);
  --color-tsp04-acc: #4f46e5;
  --color-tsp04-acc: oklch(0.511 0.23 277);
  --font-tsp04-sans: 'DM Sans', system-ui, sans-serif;
  --font-tsp04-mono: 'DM Mono', ui-monospace, monospace;
}

.tsp-04 {
  width: 100%;
  min-height: 100svh;
}
```

## JavaScript
```js
(() => {
  const root = document.querySelector('[data-tsp04]');
  if (!root || root.dataset.wired) return;
  root.dataset.wired = '1';
  const all = root.querySelector('[data-all]');
  const count = root.querySelector('[data-sel-count]');
  const rows = () => [...root.querySelectorAll('tbody tr')].filter((r) => r.offsetParent !== null);
  const sync = () => {
    const vis = rows();
    const on = vis.filter((r) => r.querySelector('[data-row-check]').checked).length;
    all.checked = vis.length > 0 && on === vis.length;
    all.indeterminate = on > 0 && on < vis.length;
    const total = root.querySelectorAll('[data-row-check]:checked').length;
    count.textContent = total + ' selected';
  };
  all.addEventListener('change', () => { rows().forEach((r) => { r.querySelector('[data-row-check]').checked = all.checked; }); sync(); });
  root.addEventListener('change', (e) => { if (e.target.matches('[data-row-check], [name=tsp-04-f]')) setTimeout(sync); });
  const save = (row) => {
    const lines = ['NORTHWIND CO — INVOICE', '', 'Invoice: ' + row.dataset.id, 'Status:  ' + row.dataset.status, 'Amount:  ' + row.dataset.amount, '', 'Thank you for your business.'];
    const url = URL.createObjectURL(new Blob([lines.join('\n')], { type: 'text/plain' }));
    const a = document.createElement('a');
    a.href = url; a.download = row.dataset.id + '.txt';
    document.body.appendChild(a); a.click(); a.remove();
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  };
  const run = (btn) => {
    if (btn.dataset.state === 'loading') return;
    btn.dataset.state = 'loading';
    setTimeout(() => {
      save(btn.closest('tr'));
      btn.dataset.state = 'done';
      setTimeout(() => { btn.dataset.state = 'idle'; }, 1800);
    }, 900);
  };
  root.querySelectorAll('[data-dl]').forEach((b) => b.addEventListener('click', () => run(b)));
  root.querySelector('[data-bulk]').addEventListener('click', () => {
    root.querySelectorAll('[data-row-check]:checked').forEach((c, i) => setTimeout(() => run(c.closest('tr').querySelector('[data-dl]')), i * 250));
  });
  sync();
})();
```

How this works

Filtering is CSS. The chips are a radio group. Each row carries data-status, and the <tbody> holds one stacked variant per status — when this chip is checked, hide every child row that isn't this status. 'All' needs no rule at all.

The script handles what CSS can't count. It keeps the select-all box honest (including indeterminate), writes the 'N selected' label, and drives each download button through data-state. The file itself is a Blob URL, so the demo downloads a real document without a server.

Make it yours

  • Add a status: give rows a new data-status, add a chip radio and one more group-has-[…]/inv:[&>tr:not(…)]:hidden class on the tbody.
  • Swap the text receipt for a PDF by pointing the download handler at your invoice endpoint.
  • Retint selection and focus with --color-tsp04-acc.

Gotchas — read before shipping

  • Arbitrary selectors can't contain spaces — Tailwind turns underscores into spaces, so [&>tr:not([data-status=paid])] is written with none.
  • The table scrolls horizontally on phones inside overflow-x-auto; keep min-w-* on the table so columns never crush.
  • Hidden rows stay selectable through select-all — the script only counts visible rows.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

:has() needs Chrome 105 / Safari 15.4 / Firefox 121. Without it every row stays visible and the bulk bar never appears; downloads still work.

Techniques used in this demo

Search CodeFronts

Loading…