03 / 13
All 13 in 13 Tailwind CSS Tables
- 01Responsive Tailwind Table That Stacks into Cards on Mobile
- 02Tailwind CSS Pricing Comparison Table with Billing Toggle
- 03Tailwind Admin Dashboard Data Table with Sticky Header, Filter & Sort
- 04Dark Neon Crypto Price Table in Tailwind CSS
- 05Tailwind Shopping Cart Table with Quantity Stepper & Live Totals
- 06Glassmorphism Striped Table in Tailwind CSS
- 07Tailwind Table with Sticky First Column & Horizontal Scroll
- 08Minimal Editorial Table Design in Tailwind CSS
- 09Tailwind Checkbox Table with :has() Row States & Progress Bar
- 10Tailwind Weekly Class Schedule Timetable
- 11Nutrition Facts Label in Tailwind CSS
- 12Neumorphic Table Design in Tailwind CSS
- 13Tailwind Table with Frosted Glass Sticky Header
Tailwind Admin Dashboard Data Table with Sticky Header, Filter & Sort
A GitHub-dark user-management grid that finally does what it draws: status filter pills that actually filter, a live search box, and sortable User and Revenue columns with real aria-sort. The header stays pinned with sticky top-0, and every visual state — pressed pill, sorted column, arrow direction — is an aria-* variant, so the script only ever sets attributes.
Mosaic by Cruip: a complete Tailwind admin dashboard template in HTML, React, Next.js and Vue · $69
Cruip · Mosaic by Cruip · Affiliate link
The code
<section class="ttb-03 grid-cols-1 w-full min-h-svh grid place-items-center bg-ttb03-bg px-5 py-14 font-ttb03-sans text-ttb03-ink" aria-label="Admin dashboard data table demo">
<div id="ttb03-grid" class="w-full max-w-5xl">
<header class="mb-5 flex flex-wrap items-center justify-between gap-4">
<div class="flex items-baseline gap-3">
<h2 class="text-lg font-semibold">User management</h2>
<span data-count role="status" class="rounded-full bg-ttb03-surface2 px-2.5 py-0.5 text-xs text-ttb03-mut ring-1 ring-ttb03-line">6 users</span>
</div>
<div class="flex flex-wrap items-center gap-2">
<label class="relative">
<span class="sr-only">Search users</span>
<svg class="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-ttb03-mut" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-3.5-3.5"/></svg>
<input type="search" placeholder="Search name or email" class="min-h-11 w-56 rounded-lg bg-ttb03-surface pr-3 pl-9 text-sm text-ttb03-ink ring-1 ring-ttb03-line transition placeholder:text-ttb03-mut focus:ring-2 focus:ring-ttb03-blue focus:outline-none">
</label>
<div class="flex gap-1 rounded-lg bg-ttb03-surface p-1 ring-1 ring-ttb03-line" role="group" aria-label="Filter by status">
<button type="button" data-filter="all" aria-pressed="true" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">All</button>
<button type="button" data-filter="active" aria-pressed="false" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Active</button>
<button type="button" data-filter="pending" aria-pressed="false" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Pending</button>
<button type="button" data-filter="trial" aria-pressed="false" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Trial</button>
<button type="button" data-filter="banned" aria-pressed="false" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Banned</button>
</div>
</div>
</header>
<div tabindex="0" role="region" aria-label="Users table, scrollable" class="relative max-h-[22rem] overflow-auto rounded-xl bg-ttb03-surface ring-1 ring-ttb03-line [scrollbar-color:var(--color-ttb03-line)_transparent] [scrollbar-width:thin] focus-visible:outline-2 focus-visible:outline-ttb03-blue">
<table class="w-full min-w-[760px] border-collapse text-left text-[13px]">
<caption class="sr-only">Users with plan, status, last activity and revenue</caption>
<thead class="[&_th]:sticky [&_th]:top-0 [&_th]:z-10 [&_th]:bg-ttb03-surface2 [&_th]:px-4 [&_th]:py-3 [&_th]:text-[11px] [&_th]:font-semibold [&_th]:tracking-[0.08em] [&_th]:text-ttb03-mut [&_th]:uppercase [&_th]:shadow-[inset_0_-1px_0_var(--color-ttb03-line)]">
<tr>
<th scope="col" aria-sort="none" class="group/th">
<button type="button" data-sort="name" class="-mx-2 inline-flex min-h-8 items-center gap-1.5 rounded px-2 uppercase transition hover:text-ttb03-ink group-aria-[sort=ascending]/th:text-ttb03-blue group-aria-[sort=descending]/th:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">User
<svg class="size-3.5 opacity-50 transition group-aria-[sort=ascending]/th:opacity-100 group-aria-[sort=descending]/th:rotate-180 group-aria-[sort=descending]/th:opacity-100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg>
</button>
</th>
<th scope="col">Email</th>
<th scope="col">Plan</th>
<th scope="col">Status</th>
<th scope="col">Last active</th>
<th scope="col" aria-sort="none" class="group/th text-right">
<button type="button" data-sort="revenue" class="-mx-2 inline-flex min-h-8 items-center gap-1.5 rounded px-2 uppercase transition hover:text-ttb03-ink group-aria-[sort=ascending]/th:text-ttb03-blue group-aria-[sort=descending]/th:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Revenue
<svg class="size-3.5 opacity-50 transition group-aria-[sort=ascending]/th:opacity-100 group-aria-[sort=descending]/th:rotate-180 group-aria-[sort=descending]/th:opacity-100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg>
</button>
</th>
<th scope="col"><span class="sr-only">Actions</span></th>
</tr>
</thead>
<tbody class="divide-y divide-ttb03-line [&_td]:px-4 [&_td]:py-3 [&_tr]:transition-colors [&_tr:hover]:bg-ttb03-blue/5">
<tr data-status="active" data-name="arya kapoor" data-revenue="4280">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-indigo-500 to-cyan-400 text-xs font-semibold text-white">AK</span><div><div class="font-semibold">Arya Kapoor</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_8841</div></div></div></td>
<td class="text-ttb03-mut">arya@nexuslab.io</td>
<td><span class="rounded-md bg-ttb03-purple/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-purple">Enterprise</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-green/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-green ring-1 ring-ttb03-green/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Active</span></td>
<td class="text-ttb03-mut">2 min ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-green tabular-nums">$4,280</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Arya Kapoor" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Arya Kapoor" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="pending" data-name="marcus reid" data-revenue="290">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-amber-400 to-red-500 text-xs font-semibold text-white">MR</span><div><div class="font-semibold">Marcus Reid</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_7722</div></div></div></td>
<td class="text-ttb03-mut">m.reid@agency.co</td>
<td><span class="rounded-md bg-ttb03-blue/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-blue">Pro</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-yellow/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-yellow ring-1 ring-ttb03-yellow/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Pending</span></td>
<td class="text-ttb03-mut">1 hr ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-ink tabular-nums">$290</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Marcus Reid" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Marcus Reid" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="active" data-name="selin novak" data-revenue="580">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-emerald-500 to-cyan-500 text-xs font-semibold text-white">SN</span><div><div class="font-semibold">Selin Novak</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_6610</div></div></div></td>
<td class="text-ttb03-mut">selin@design.studio</td>
<td><span class="rounded-md bg-ttb03-blue/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-blue">Pro</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-green/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-green ring-1 ring-ttb03-green/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Active</span></td>
<td class="text-ttb03-mut">4 hrs ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-green tabular-nums">$580</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Selin Novak" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Selin Novak" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="trial" data-name="theo walsh" data-revenue="0">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-violet-500 to-pink-500 text-xs font-semibold text-white">TW</span><div><div class="font-semibold">Theo Walsh</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_5503</div></div></div></td>
<td class="text-ttb03-mut">theo@startup.xyz</td>
<td><span class="rounded-md bg-ttb03-surface2 px-2 py-0.5 text-[11px] font-semibold text-ttb03-mut">Free</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-purple/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-purple ring-1 ring-ttb03-purple/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Trial</span></td>
<td class="text-ttb03-mut">2 days ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-mut tabular-nums">$0</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Theo Walsh" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Theo Walsh" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="banned" data-name="lena kim" data-revenue="-120">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-rose-500 to-rose-300 text-xs font-semibold text-white">LK</span><div><div class="font-semibold">Lena Kim</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_4491</div></div></div></td>
<td class="text-ttb03-mut">lena.kim@corp.io</td>
<td><span class="rounded-md bg-ttb03-surface2 px-2 py-0.5 text-[11px] font-semibold text-ttb03-mut">Free</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-red/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-red ring-1 ring-ttb03-red/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Banned</span></td>
<td class="text-ttb03-mut">14 days ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-red tabular-nums">−$120</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Lena Kim" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Lena Kim" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="active" data-name="james park" data-revenue="12400">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-sky-500 to-sky-300 text-xs font-semibold text-white">JP</span><div><div class="font-semibold">James Park</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_3380</div></div></div></td>
<td class="text-ttb03-mut">jpark@techco.dev</td>
<td><span class="rounded-md bg-ttb03-purple/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-purple">Enterprise</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-green/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-green ring-1 ring-ttb03-green/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Active</span></td>
<td class="text-ttb03-mut">Just now</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-green tabular-nums">$12,400</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit James Park" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for James Park" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-empty hidden><td colspan="7" class="py-12! text-center text-ttb03-mut">No users match this filter.</td></tr>
</tbody>
</table>
</div>
</div>
</section><section class="ttb-03 grid-cols-1 w-full min-h-svh grid place-items-center bg-ttb03-bg px-5 py-14 font-ttb03-sans text-ttb03-ink" aria-label="Admin dashboard data table demo">
<div id="ttb03-grid" class="w-full max-w-5xl">
<header class="mb-5 flex flex-wrap items-center justify-between gap-4">
<div class="flex items-baseline gap-3">
<h2 class="text-lg font-semibold">User management</h2>
<span data-count role="status" class="rounded-full bg-ttb03-surface2 px-2.5 py-0.5 text-xs text-ttb03-mut ring-1 ring-ttb03-line">6 users</span>
</div>
<div class="flex flex-wrap items-center gap-2">
<label class="relative">
<span class="sr-only">Search users</span>
<svg class="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-ttb03-mut" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-3.5-3.5"/></svg>
<input type="search" placeholder="Search name or email" class="min-h-11 w-56 rounded-lg bg-ttb03-surface pr-3 pl-9 text-sm text-ttb03-ink ring-1 ring-ttb03-line transition placeholder:text-ttb03-mut focus:ring-2 focus:ring-ttb03-blue focus:outline-none">
</label>
<div class="flex gap-1 rounded-lg bg-ttb03-surface p-1 ring-1 ring-ttb03-line" role="group" aria-label="Filter by status">
<button type="button" data-filter="all" aria-pressed="true" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">All</button>
<button type="button" data-filter="active" aria-pressed="false" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Active</button>
<button type="button" data-filter="pending" aria-pressed="false" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Pending</button>
<button type="button" data-filter="trial" aria-pressed="false" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Trial</button>
<button type="button" data-filter="banned" aria-pressed="false" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Banned</button>
</div>
</div>
</header>
<div tabindex="0" role="region" aria-label="Users table, scrollable" class="relative max-h-[22rem] overflow-auto rounded-xl bg-ttb03-surface ring-1 ring-ttb03-line [scrollbar-color:var(--color-ttb03-line)_transparent] [scrollbar-width:thin] focus-visible:outline-2 focus-visible:outline-ttb03-blue">
<table class="w-full min-w-[760px] border-collapse text-left text-[13px]">
<caption class="sr-only">Users with plan, status, last activity and revenue</caption>
<thead class="[&_th]:sticky [&_th]:top-0 [&_th]:z-10 [&_th]:bg-ttb03-surface2 [&_th]:px-4 [&_th]:py-3 [&_th]:text-[11px] [&_th]:font-semibold [&_th]:tracking-[0.08em] [&_th]:text-ttb03-mut [&_th]:uppercase [&_th]:shadow-[inset_0_-1px_0_var(--color-ttb03-line)]">
<tr>
<th scope="col" aria-sort="none" class="group/th">
<button type="button" data-sort="name" class="-mx-2 inline-flex min-h-8 items-center gap-1.5 rounded px-2 uppercase transition hover:text-ttb03-ink group-aria-[sort=ascending]/th:text-ttb03-blue group-aria-[sort=descending]/th:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">User
<svg class="size-3.5 opacity-50 transition group-aria-[sort=ascending]/th:opacity-100 group-aria-[sort=descending]/th:rotate-180 group-aria-[sort=descending]/th:opacity-100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg>
</button>
</th>
<th scope="col">Email</th>
<th scope="col">Plan</th>
<th scope="col">Status</th>
<th scope="col">Last active</th>
<th scope="col" aria-sort="none" class="group/th text-right">
<button type="button" data-sort="revenue" class="-mx-2 inline-flex min-h-8 items-center gap-1.5 rounded px-2 uppercase transition hover:text-ttb03-ink group-aria-[sort=ascending]/th:text-ttb03-blue group-aria-[sort=descending]/th:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Revenue
<svg class="size-3.5 opacity-50 transition group-aria-[sort=ascending]/th:opacity-100 group-aria-[sort=descending]/th:rotate-180 group-aria-[sort=descending]/th:opacity-100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg>
</button>
</th>
<th scope="col"><span class="sr-only">Actions</span></th>
</tr>
</thead>
<tbody class="divide-y divide-ttb03-line [&_td]:px-4 [&_td]:py-3 [&_tr]:transition-colors [&_tr:hover]:bg-ttb03-blue/5">
<tr data-status="active" data-name="arya kapoor" data-revenue="4280">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-indigo-500 to-cyan-400 text-xs font-semibold text-white">AK</span><div><div class="font-semibold">Arya Kapoor</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_8841</div></div></div></td>
<td class="text-ttb03-mut">arya@nexuslab.io</td>
<td><span class="rounded-md bg-ttb03-purple/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-purple">Enterprise</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-green/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-green ring-1 ring-ttb03-green/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Active</span></td>
<td class="text-ttb03-mut">2 min ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-green tabular-nums">$4,280</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Arya Kapoor" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Arya Kapoor" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="pending" data-name="marcus reid" data-revenue="290">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-amber-400 to-red-500 text-xs font-semibold text-white">MR</span><div><div class="font-semibold">Marcus Reid</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_7722</div></div></div></td>
<td class="text-ttb03-mut">m.reid@agency.co</td>
<td><span class="rounded-md bg-ttb03-blue/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-blue">Pro</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-yellow/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-yellow ring-1 ring-ttb03-yellow/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Pending</span></td>
<td class="text-ttb03-mut">1 hr ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-ink tabular-nums">$290</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Marcus Reid" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Marcus Reid" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="active" data-name="selin novak" data-revenue="580">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-emerald-500 to-cyan-500 text-xs font-semibold text-white">SN</span><div><div class="font-semibold">Selin Novak</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_6610</div></div></div></td>
<td class="text-ttb03-mut">selin@design.studio</td>
<td><span class="rounded-md bg-ttb03-blue/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-blue">Pro</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-green/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-green ring-1 ring-ttb03-green/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Active</span></td>
<td class="text-ttb03-mut">4 hrs ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-green tabular-nums">$580</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Selin Novak" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Selin Novak" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="trial" data-name="theo walsh" data-revenue="0">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-violet-500 to-pink-500 text-xs font-semibold text-white">TW</span><div><div class="font-semibold">Theo Walsh</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_5503</div></div></div></td>
<td class="text-ttb03-mut">theo@startup.xyz</td>
<td><span class="rounded-md bg-ttb03-surface2 px-2 py-0.5 text-[11px] font-semibold text-ttb03-mut">Free</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-purple/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-purple ring-1 ring-ttb03-purple/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Trial</span></td>
<td class="text-ttb03-mut">2 days ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-mut tabular-nums">$0</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Theo Walsh" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Theo Walsh" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="banned" data-name="lena kim" data-revenue="-120">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-rose-500 to-rose-300 text-xs font-semibold text-white">LK</span><div><div class="font-semibold">Lena Kim</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_4491</div></div></div></td>
<td class="text-ttb03-mut">lena.kim@corp.io</td>
<td><span class="rounded-md bg-ttb03-surface2 px-2 py-0.5 text-[11px] font-semibold text-ttb03-mut">Free</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-red/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-red ring-1 ring-ttb03-red/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Banned</span></td>
<td class="text-ttb03-mut">14 days ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-red tabular-nums">−$120</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Lena Kim" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Lena Kim" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="active" data-name="james park" data-revenue="12400">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-sky-500 to-sky-300 text-xs font-semibold text-white">JP</span><div><div class="font-semibold">James Park</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_3380</div></div></div></td>
<td class="text-ttb03-mut">jpark@techco.dev</td>
<td><span class="rounded-md bg-ttb03-purple/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-purple">Enterprise</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-green/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-green ring-1 ring-ttb03-green/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Active</span></td>
<td class="text-ttb03-mut">Just now</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-green tabular-nums">$12,400</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit James Park" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for James Park" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-empty hidden><td colspan="7" class="py-12! text-center text-ttb03-mut">No users match this filter.</td></tr>
</tbody>
</table>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-ttb03-bg: #0d1117;
--color-ttb03-bg: oklch(0.18 0.012 260);
--color-ttb03-surface: #161b22;
--color-ttb03-surface: oklch(0.21 0.013 260);
--color-ttb03-surface2: #21262d;
--color-ttb03-surface2: oklch(0.26 0.012 260);
--color-ttb03-line: #30363d;
--color-ttb03-line: oklch(0.34 0.012 260);
--color-ttb03-ink: #e6edf3;
--color-ttb03-ink: oklch(0.94 0.01 240);
--color-ttb03-mut: #9198a1;
--color-ttb03-mut: oklch(0.68 0.015 250);
--color-ttb03-blue: #58a6ff;
--color-ttb03-blue: oklch(0.72 0.15 255);
--color-ttb03-green: #3fb950;
--color-ttb03-green: oklch(0.7 0.17 145);
--color-ttb03-yellow: #d29922;
--color-ttb03-yellow: oklch(0.71 0.14 80);
--color-ttb03-red: #f85149;
--color-ttb03-red: oklch(0.66 0.2 25);
--color-ttb03-purple: #bc8cff;
--color-ttb03-purple: oklch(0.73 0.15 300);
--font-ttb03-sans: 'IBM Plex Sans', system-ui, sans-serif;
--font-ttb03-mono: 'IBM Plex Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.ttb-03 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-ttb03-bg: #0d1117;
--color-ttb03-bg: oklch(0.18 0.012 260);
--color-ttb03-surface: #161b22;
--color-ttb03-surface: oklch(0.21 0.013 260);
--color-ttb03-surface2: #21262d;
--color-ttb03-surface2: oklch(0.26 0.012 260);
--color-ttb03-line: #30363d;
--color-ttb03-line: oklch(0.34 0.012 260);
--color-ttb03-ink: #e6edf3;
--color-ttb03-ink: oklch(0.94 0.01 240);
--color-ttb03-mut: #9198a1;
--color-ttb03-mut: oklch(0.68 0.015 250);
--color-ttb03-blue: #58a6ff;
--color-ttb03-blue: oklch(0.72 0.15 255);
--color-ttb03-green: #3fb950;
--color-ttb03-green: oklch(0.7 0.17 145);
--color-ttb03-yellow: #d29922;
--color-ttb03-yellow: oklch(0.71 0.14 80);
--color-ttb03-red: #f85149;
--color-ttb03-red: oklch(0.66 0.2 25);
--color-ttb03-purple: #bc8cff;
--color-ttb03-purple: oklch(0.73 0.15 300);
--font-ttb03-sans: 'IBM Plex Sans', system-ui, sans-serif;
--font-ttb03-mono: 'IBM Plex Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.ttb-03 {
width: 100%;
min-height: 100svh;
}(() => {
const root = document.getElementById('ttb03-grid');
if (!root || root.dataset.wired) return;
root.dataset.wired = '1';
const body = root.querySelector('tbody');
const rows = Array.from(body.querySelectorAll('tr[data-status]'));
const empty = body.querySelector('[data-empty]');
const count = root.querySelector('[data-count]');
const search = root.querySelector('input[type="search"]');
let filter = 'all';
const apply = () => {
const q = search.value.trim().toLowerCase();
let n = 0;
rows.forEach((r) => {
const ok = (filter === 'all' || r.dataset.status === filter) && r.textContent.toLowerCase().includes(q);
r.hidden = !ok;
if (ok) n++;
});
empty.hidden = n !== 0;
count.textContent = n + (n === 1 ? ' user' : ' users');
};
root.querySelectorAll('[data-filter]').forEach((btn) => btn.addEventListener('click', () => {
root.querySelectorAll('[data-filter]').forEach((b) => b.setAttribute('aria-pressed', String(b === btn)));
filter = btn.dataset.filter;
apply();
}));
search.addEventListener('input', apply);
root.querySelectorAll('[data-sort]').forEach((btn) => btn.addEventListener('click', () => {
const th = btn.closest('th');
const dir = th.getAttribute('aria-sort') === 'ascending' ? 'descending' : 'ascending';
root.querySelectorAll('th[aria-sort]').forEach((t) => t.setAttribute('aria-sort', 'none'));
th.setAttribute('aria-sort', dir);
const key = btn.dataset.sort;
const val = (r) => key === 'revenue' ? Number(r.dataset.revenue) : r.dataset.name;
rows.sort((a, b) => {
const x = val(a), y = val(b);
const c = x > y ? 1 : x < y ? -1 : 0;
return dir === 'ascending' ? c : -c;
});
rows.forEach((r) => body.insertBefore(r, empty));
}));
apply();
})();(() => {
const root = document.getElementById('ttb03-grid');
if (!root || root.dataset.wired) return;
root.dataset.wired = '1';
const body = root.querySelector('tbody');
const rows = Array.from(body.querySelectorAll('tr[data-status]'));
const empty = body.querySelector('[data-empty]');
const count = root.querySelector('[data-count]');
const search = root.querySelector('input[type="search"]');
let filter = 'all';
const apply = () => {
const q = search.value.trim().toLowerCase();
let n = 0;
rows.forEach((r) => {
const ok = (filter === 'all' || r.dataset.status === filter) && r.textContent.toLowerCase().includes(q);
r.hidden = !ok;
if (ok) n++;
});
empty.hidden = n !== 0;
count.textContent = n + (n === 1 ? ' user' : ' users');
};
root.querySelectorAll('[data-filter]').forEach((btn) => btn.addEventListener('click', () => {
root.querySelectorAll('[data-filter]').forEach((b) => b.setAttribute('aria-pressed', String(b === btn)));
filter = btn.dataset.filter;
apply();
}));
search.addEventListener('input', apply);
root.querySelectorAll('[data-sort]').forEach((btn) => btn.addEventListener('click', () => {
const th = btn.closest('th');
const dir = th.getAttribute('aria-sort') === 'ascending' ? 'descending' : 'ascending';
root.querySelectorAll('th[aria-sort]').forEach((t) => t.setAttribute('aria-sort', 'none'));
th.setAttribute('aria-sort', dir);
const key = btn.dataset.sort;
const val = (r) => key === 'revenue' ? Number(r.dataset.revenue) : r.dataset.name;
rows.sort((a, b) => {
const x = val(a), y = val(b);
const c = x > y ? 1 : x < y ? -1 : 0;
return dir === 'ascending' ? c : -c;
});
rows.forEach((r) => body.insertBefore(r, empty));
}));
apply();
})();Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<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.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-ttb03-bg: #0d1117;
--color-ttb03-bg: oklch(0.18 0.012 260);
--color-ttb03-surface: #161b22;
--color-ttb03-surface: oklch(0.21 0.013 260);
--color-ttb03-surface2: #21262d;
--color-ttb03-surface2: oklch(0.26 0.012 260);
--color-ttb03-line: #30363d;
--color-ttb03-line: oklch(0.34 0.012 260);
--color-ttb03-ink: #e6edf3;
--color-ttb03-ink: oklch(0.94 0.01 240);
--color-ttb03-mut: #9198a1;
--color-ttb03-mut: oklch(0.68 0.015 250);
--color-ttb03-blue: #58a6ff;
--color-ttb03-blue: oklch(0.72 0.15 255);
--color-ttb03-green: #3fb950;
--color-ttb03-green: oklch(0.7 0.17 145);
--color-ttb03-yellow: #d29922;
--color-ttb03-yellow: oklch(0.71 0.14 80);
--color-ttb03-red: #f85149;
--color-ttb03-red: oklch(0.66 0.2 25);
--color-ttb03-purple: #bc8cff;
--color-ttb03-purple: oklch(0.73 0.15 300);
--font-ttb03-sans: 'IBM Plex Sans', system-ui, sans-serif;
--font-ttb03-mono: 'IBM Plex Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.ttb-03 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-ttb03-bg: #0d1117;
--color-ttb03-bg: oklch(0.18 0.012 260);
--color-ttb03-surface: #161b22;
--color-ttb03-surface: oklch(0.21 0.013 260);
--color-ttb03-surface2: #21262d;
--color-ttb03-surface2: oklch(0.26 0.012 260);
--color-ttb03-line: #30363d;
--color-ttb03-line: oklch(0.34 0.012 260);
--color-ttb03-ink: #e6edf3;
--color-ttb03-ink: oklch(0.94 0.01 240);
--color-ttb03-mut: #9198a1;
--color-ttb03-mut: oklch(0.68 0.015 250);
--color-ttb03-blue: #58a6ff;
--color-ttb03-blue: oklch(0.72 0.15 255);
--color-ttb03-green: #3fb950;
--color-ttb03-green: oklch(0.7 0.17 145);
--color-ttb03-yellow: #d29922;
--color-ttb03-yellow: oklch(0.71 0.14 80);
--color-ttb03-red: #f85149;
--color-ttb03-red: oklch(0.66 0.2 25);
--color-ttb03-purple: #bc8cff;
--color-ttb03-purple: oklch(0.73 0.15 300);
--font-ttb03-sans: 'IBM Plex Sans', system-ui, sans-serif;
--font-ttb03-mono: 'IBM Plex Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.ttb-03 {
width: 100%;
min-height: 100svh;
}<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=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap">
<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=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
.ct-03__filter-btn--activebecamearia-pressed+ thearia-pressed:variant;th.ct-03--sorted::after { content:' ↑' }became a realaria-sortheader whose SVG arrow flips withgroup-aria-[sort=descending]/th:rotate-180.thead { position:sticky }moved to the cells:[&_th]:sticky [&_th]:top-0, the spelling that survives Firefox and older Safari. The divider is an inset shadow so it stays pinned with the header.- UI upgrade from the CSS original: the filter pills only changed colour before — now they filter, a search box narrows by name or email, two columns sort, the user count is a live
role="status", and an empty state appears when nothing matches. - UI upgrade: emoji action buttons (✏️ 👁 ⋮) replaced by aria-labelled SVG buttons with a 44px hit area via
after:-inset-1.5; the scroll region is keyboard-focusable and labelled; muted copy raised from#7d8590to 5.8:1.
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 Admin Dashboard Data Table with Sticky Header, Filter & Sort
Source: https://codefronts.com/snippets/tailwind-css-tables/tailwind-admin-dashboard-data-table-sticky-header-filter-sort/
A GitHub-dark user-management grid that finally does what it draws: status filter pills that actually filter, a live search box, and sortable User and Revenue columns with real aria-sort. The header stays pinned with sticky top-0, and every visual state — pressed pill, sorted column, arrow direction — is an aria-* variant, so the script only ever sets attributes.
## HTML
```html
<section class="ttb-03 grid-cols-1 w-full min-h-svh grid place-items-center bg-ttb03-bg px-5 py-14 font-ttb03-sans text-ttb03-ink" aria-label="Admin dashboard data table demo">
<div id="ttb03-grid" class="w-full max-w-5xl">
<header class="mb-5 flex flex-wrap items-center justify-between gap-4">
<div class="flex items-baseline gap-3">
<h2 class="text-lg font-semibold">User management</h2>
<span data-count role="status" class="rounded-full bg-ttb03-surface2 px-2.5 py-0.5 text-xs text-ttb03-mut ring-1 ring-ttb03-line">6 users</span>
</div>
<div class="flex flex-wrap items-center gap-2">
<label class="relative">
<span class="sr-only">Search users</span>
<svg class="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-ttb03-mut" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-3.5-3.5"/></svg>
<input type="search" placeholder="Search name or email" class="min-h-11 w-56 rounded-lg bg-ttb03-surface pr-3 pl-9 text-sm text-ttb03-ink ring-1 ring-ttb03-line transition placeholder:text-ttb03-mut focus:ring-2 focus:ring-ttb03-blue focus:outline-none">
</label>
<div class="flex gap-1 rounded-lg bg-ttb03-surface p-1 ring-1 ring-ttb03-line" role="group" aria-label="Filter by status">
<button type="button" data-filter="all" aria-pressed="true" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">All</button>
<button type="button" data-filter="active" aria-pressed="false" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Active</button>
<button type="button" data-filter="pending" aria-pressed="false" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Pending</button>
<button type="button" data-filter="trial" aria-pressed="false" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Trial</button>
<button type="button" data-filter="banned" aria-pressed="false" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Banned</button>
</div>
</div>
</header>
<div tabindex="0" role="region" aria-label="Users table, scrollable" class="relative max-h-[22rem] overflow-auto rounded-xl bg-ttb03-surface ring-1 ring-ttb03-line [scrollbar-color:var(--color-ttb03-line)_transparent] [scrollbar-width:thin] focus-visible:outline-2 focus-visible:outline-ttb03-blue">
<table class="w-full min-w-[760px] border-collapse text-left text-[13px]">
<caption class="sr-only">Users with plan, status, last activity and revenue</caption>
<thead class="[&_th]:sticky [&_th]:top-0 [&_th]:z-10 [&_th]:bg-ttb03-surface2 [&_th]:px-4 [&_th]:py-3 [&_th]:text-[11px] [&_th]:font-semibold [&_th]:tracking-[0.08em] [&_th]:text-ttb03-mut [&_th]:uppercase [&_th]:shadow-[inset_0_-1px_0_var(--color-ttb03-line)]">
<tr>
<th scope="col" aria-sort="none" class="group/th">
<button type="button" data-sort="name" class="-mx-2 inline-flex min-h-8 items-center gap-1.5 rounded px-2 uppercase transition hover:text-ttb03-ink group-aria-[sort=ascending]/th:text-ttb03-blue group-aria-[sort=descending]/th:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">User
<svg class="size-3.5 opacity-50 transition group-aria-[sort=ascending]/th:opacity-100 group-aria-[sort=descending]/th:rotate-180 group-aria-[sort=descending]/th:opacity-100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg>
</button>
</th>
<th scope="col">Email</th>
<th scope="col">Plan</th>
<th scope="col">Status</th>
<th scope="col">Last active</th>
<th scope="col" aria-sort="none" class="group/th text-right">
<button type="button" data-sort="revenue" class="-mx-2 inline-flex min-h-8 items-center gap-1.5 rounded px-2 uppercase transition hover:text-ttb03-ink group-aria-[sort=ascending]/th:text-ttb03-blue group-aria-[sort=descending]/th:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Revenue
<svg class="size-3.5 opacity-50 transition group-aria-[sort=ascending]/th:opacity-100 group-aria-[sort=descending]/th:rotate-180 group-aria-[sort=descending]/th:opacity-100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg>
</button>
</th>
<th scope="col"><span class="sr-only">Actions</span></th>
</tr>
</thead>
<tbody class="divide-y divide-ttb03-line [&_td]:px-4 [&_td]:py-3 [&_tr]:transition-colors [&_tr:hover]:bg-ttb03-blue/5">
<tr data-status="active" data-name="arya kapoor" data-revenue="4280">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-indigo-500 to-cyan-400 text-xs font-semibold text-white">AK</span><div><div class="font-semibold">Arya Kapoor</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_8841</div></div></div></td>
<td class="text-ttb03-mut">arya@nexuslab.io</td>
<td><span class="rounded-md bg-ttb03-purple/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-purple">Enterprise</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-green/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-green ring-1 ring-ttb03-green/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Active</span></td>
<td class="text-ttb03-mut">2 min ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-green tabular-nums">$4,280</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Arya Kapoor" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Arya Kapoor" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="pending" data-name="marcus reid" data-revenue="290">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-amber-400 to-red-500 text-xs font-semibold text-white">MR</span><div><div class="font-semibold">Marcus Reid</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_7722</div></div></div></td>
<td class="text-ttb03-mut">m.reid@agency.co</td>
<td><span class="rounded-md bg-ttb03-blue/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-blue">Pro</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-yellow/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-yellow ring-1 ring-ttb03-yellow/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Pending</span></td>
<td class="text-ttb03-mut">1 hr ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-ink tabular-nums">$290</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Marcus Reid" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Marcus Reid" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="active" data-name="selin novak" data-revenue="580">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-emerald-500 to-cyan-500 text-xs font-semibold text-white">SN</span><div><div class="font-semibold">Selin Novak</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_6610</div></div></div></td>
<td class="text-ttb03-mut">selin@design.studio</td>
<td><span class="rounded-md bg-ttb03-blue/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-blue">Pro</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-green/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-green ring-1 ring-ttb03-green/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Active</span></td>
<td class="text-ttb03-mut">4 hrs ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-green tabular-nums">$580</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Selin Novak" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Selin Novak" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="trial" data-name="theo walsh" data-revenue="0">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-violet-500 to-pink-500 text-xs font-semibold text-white">TW</span><div><div class="font-semibold">Theo Walsh</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_5503</div></div></div></td>
<td class="text-ttb03-mut">theo@startup.xyz</td>
<td><span class="rounded-md bg-ttb03-surface2 px-2 py-0.5 text-[11px] font-semibold text-ttb03-mut">Free</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-purple/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-purple ring-1 ring-ttb03-purple/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Trial</span></td>
<td class="text-ttb03-mut">2 days ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-mut tabular-nums">$0</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Theo Walsh" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Theo Walsh" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="banned" data-name="lena kim" data-revenue="-120">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-rose-500 to-rose-300 text-xs font-semibold text-white">LK</span><div><div class="font-semibold">Lena Kim</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_4491</div></div></div></td>
<td class="text-ttb03-mut">lena.kim@corp.io</td>
<td><span class="rounded-md bg-ttb03-surface2 px-2 py-0.5 text-[11px] font-semibold text-ttb03-mut">Free</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-red/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-red ring-1 ring-ttb03-red/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Banned</span></td>
<td class="text-ttb03-mut">14 days ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-red tabular-nums">−$120</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Lena Kim" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Lena Kim" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="active" data-name="james park" data-revenue="12400">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-sky-500 to-sky-300 text-xs font-semibold text-white">JP</span><div><div class="font-semibold">James Park</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_3380</div></div></div></td>
<td class="text-ttb03-mut">jpark@techco.dev</td>
<td><span class="rounded-md bg-ttb03-purple/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-purple">Enterprise</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-green/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-green ring-1 ring-ttb03-green/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Active</span></td>
<td class="text-ttb03-mut">Just now</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-green tabular-nums">$12,400</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit James Park" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for James Park" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-empty hidden><td colspan="7" class="py-12! text-center text-ttb03-mut">No users match this filter.</td></tr>
</tbody>
</table>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-ttb03-bg: #0d1117;
--color-ttb03-bg: oklch(0.18 0.012 260);
--color-ttb03-surface: #161b22;
--color-ttb03-surface: oklch(0.21 0.013 260);
--color-ttb03-surface2: #21262d;
--color-ttb03-surface2: oklch(0.26 0.012 260);
--color-ttb03-line: #30363d;
--color-ttb03-line: oklch(0.34 0.012 260);
--color-ttb03-ink: #e6edf3;
--color-ttb03-ink: oklch(0.94 0.01 240);
--color-ttb03-mut: #9198a1;
--color-ttb03-mut: oklch(0.68 0.015 250);
--color-ttb03-blue: #58a6ff;
--color-ttb03-blue: oklch(0.72 0.15 255);
--color-ttb03-green: #3fb950;
--color-ttb03-green: oklch(0.7 0.17 145);
--color-ttb03-yellow: #d29922;
--color-ttb03-yellow: oklch(0.71 0.14 80);
--color-ttb03-red: #f85149;
--color-ttb03-red: oklch(0.66 0.2 25);
--color-ttb03-purple: #bc8cff;
--color-ttb03-purple: oklch(0.73 0.15 300);
--font-ttb03-sans: 'IBM Plex Sans', system-ui, sans-serif;
--font-ttb03-mono: 'IBM Plex Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.ttb-03 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
const root = document.getElementById('ttb03-grid');
if (!root || root.dataset.wired) return;
root.dataset.wired = '1';
const body = root.querySelector('tbody');
const rows = Array.from(body.querySelectorAll('tr[data-status]'));
const empty = body.querySelector('[data-empty]');
const count = root.querySelector('[data-count]');
const search = root.querySelector('input[type="search"]');
let filter = 'all';
const apply = () => {
const q = search.value.trim().toLowerCase();
let n = 0;
rows.forEach((r) => {
const ok = (filter === 'all' || r.dataset.status === filter) && r.textContent.toLowerCase().includes(q);
r.hidden = !ok;
if (ok) n++;
});
empty.hidden = n !== 0;
count.textContent = n + (n === 1 ? ' user' : ' users');
};
root.querySelectorAll('[data-filter]').forEach((btn) => btn.addEventListener('click', () => {
root.querySelectorAll('[data-filter]').forEach((b) => b.setAttribute('aria-pressed', String(b === btn)));
filter = btn.dataset.filter;
apply();
}));
search.addEventListener('input', apply);
root.querySelectorAll('[data-sort]').forEach((btn) => btn.addEventListener('click', () => {
const th = btn.closest('th');
const dir = th.getAttribute('aria-sort') === 'ascending' ? 'descending' : 'ascending';
root.querySelectorAll('th[aria-sort]').forEach((t) => t.setAttribute('aria-sort', 'none'));
th.setAttribute('aria-sort', dir);
const key = btn.dataset.sort;
const val = (r) => key === 'revenue' ? Number(r.dataset.revenue) : r.dataset.name;
rows.sort((a, b) => {
const x = val(a), y = val(b);
const c = x > y ? 1 : x < y ? -1 : 0;
return dir === 'ascending' ? c : -c;
});
rows.forEach((r) => body.insertBefore(r, empty));
}));
apply();
})();
```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 Admin Dashboard Data Table with Sticky Header, Filter & Sort
Source: https://codefronts.com/snippets/tailwind-css-tables/tailwind-admin-dashboard-data-table-sticky-header-filter-sort/
A GitHub-dark user-management grid that finally does what it draws: status filter pills that actually filter, a live search box, and sortable User and Revenue columns with real aria-sort. The header stays pinned with sticky top-0, and every visual state — pressed pill, sorted column, arrow direction — is an aria-* variant, so the script only ever sets attributes.
## HTML
```html
<section class="ttb-03 grid-cols-1 w-full min-h-svh grid place-items-center bg-ttb03-bg px-5 py-14 font-ttb03-sans text-ttb03-ink" aria-label="Admin dashboard data table demo">
<div id="ttb03-grid" class="w-full max-w-5xl">
<header class="mb-5 flex flex-wrap items-center justify-between gap-4">
<div class="flex items-baseline gap-3">
<h2 class="text-lg font-semibold">User management</h2>
<span data-count role="status" class="rounded-full bg-ttb03-surface2 px-2.5 py-0.5 text-xs text-ttb03-mut ring-1 ring-ttb03-line">6 users</span>
</div>
<div class="flex flex-wrap items-center gap-2">
<label class="relative">
<span class="sr-only">Search users</span>
<svg class="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-ttb03-mut" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-3.5-3.5"/></svg>
<input type="search" placeholder="Search name or email" class="min-h-11 w-56 rounded-lg bg-ttb03-surface pr-3 pl-9 text-sm text-ttb03-ink ring-1 ring-ttb03-line transition placeholder:text-ttb03-mut focus:ring-2 focus:ring-ttb03-blue focus:outline-none">
</label>
<div class="flex gap-1 rounded-lg bg-ttb03-surface p-1 ring-1 ring-ttb03-line" role="group" aria-label="Filter by status">
<button type="button" data-filter="all" aria-pressed="true" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">All</button>
<button type="button" data-filter="active" aria-pressed="false" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Active</button>
<button type="button" data-filter="pending" aria-pressed="false" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Pending</button>
<button type="button" data-filter="trial" aria-pressed="false" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Trial</button>
<button type="button" data-filter="banned" aria-pressed="false" class="min-h-9 rounded-md px-3 text-xs font-semibold text-ttb03-mut transition hover:text-ttb03-ink aria-pressed:bg-ttb03-blue/12 aria-pressed:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Banned</button>
</div>
</div>
</header>
<div tabindex="0" role="region" aria-label="Users table, scrollable" class="relative max-h-[22rem] overflow-auto rounded-xl bg-ttb03-surface ring-1 ring-ttb03-line [scrollbar-color:var(--color-ttb03-line)_transparent] [scrollbar-width:thin] focus-visible:outline-2 focus-visible:outline-ttb03-blue">
<table class="w-full min-w-[760px] border-collapse text-left text-[13px]">
<caption class="sr-only">Users with plan, status, last activity and revenue</caption>
<thead class="[&_th]:sticky [&_th]:top-0 [&_th]:z-10 [&_th]:bg-ttb03-surface2 [&_th]:px-4 [&_th]:py-3 [&_th]:text-[11px] [&_th]:font-semibold [&_th]:tracking-[0.08em] [&_th]:text-ttb03-mut [&_th]:uppercase [&_th]:shadow-[inset_0_-1px_0_var(--color-ttb03-line)]">
<tr>
<th scope="col" aria-sort="none" class="group/th">
<button type="button" data-sort="name" class="-mx-2 inline-flex min-h-8 items-center gap-1.5 rounded px-2 uppercase transition hover:text-ttb03-ink group-aria-[sort=ascending]/th:text-ttb03-blue group-aria-[sort=descending]/th:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">User
<svg class="size-3.5 opacity-50 transition group-aria-[sort=ascending]/th:opacity-100 group-aria-[sort=descending]/th:rotate-180 group-aria-[sort=descending]/th:opacity-100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg>
</button>
</th>
<th scope="col">Email</th>
<th scope="col">Plan</th>
<th scope="col">Status</th>
<th scope="col">Last active</th>
<th scope="col" aria-sort="none" class="group/th text-right">
<button type="button" data-sort="revenue" class="-mx-2 inline-flex min-h-8 items-center gap-1.5 rounded px-2 uppercase transition hover:text-ttb03-ink group-aria-[sort=ascending]/th:text-ttb03-blue group-aria-[sort=descending]/th:text-ttb03-blue focus-visible:outline-2 focus-visible:outline-ttb03-blue">Revenue
<svg class="size-3.5 opacity-50 transition group-aria-[sort=ascending]/th:opacity-100 group-aria-[sort=descending]/th:rotate-180 group-aria-[sort=descending]/th:opacity-100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M12 19V5M6 11l6-6 6 6"/></svg>
</button>
</th>
<th scope="col"><span class="sr-only">Actions</span></th>
</tr>
</thead>
<tbody class="divide-y divide-ttb03-line [&_td]:px-4 [&_td]:py-3 [&_tr]:transition-colors [&_tr:hover]:bg-ttb03-blue/5">
<tr data-status="active" data-name="arya kapoor" data-revenue="4280">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-indigo-500 to-cyan-400 text-xs font-semibold text-white">AK</span><div><div class="font-semibold">Arya Kapoor</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_8841</div></div></div></td>
<td class="text-ttb03-mut">arya@nexuslab.io</td>
<td><span class="rounded-md bg-ttb03-purple/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-purple">Enterprise</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-green/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-green ring-1 ring-ttb03-green/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Active</span></td>
<td class="text-ttb03-mut">2 min ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-green tabular-nums">$4,280</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Arya Kapoor" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Arya Kapoor" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="pending" data-name="marcus reid" data-revenue="290">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-amber-400 to-red-500 text-xs font-semibold text-white">MR</span><div><div class="font-semibold">Marcus Reid</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_7722</div></div></div></td>
<td class="text-ttb03-mut">m.reid@agency.co</td>
<td><span class="rounded-md bg-ttb03-blue/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-blue">Pro</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-yellow/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-yellow ring-1 ring-ttb03-yellow/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Pending</span></td>
<td class="text-ttb03-mut">1 hr ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-ink tabular-nums">$290</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Marcus Reid" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Marcus Reid" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="active" data-name="selin novak" data-revenue="580">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-emerald-500 to-cyan-500 text-xs font-semibold text-white">SN</span><div><div class="font-semibold">Selin Novak</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_6610</div></div></div></td>
<td class="text-ttb03-mut">selin@design.studio</td>
<td><span class="rounded-md bg-ttb03-blue/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-blue">Pro</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-green/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-green ring-1 ring-ttb03-green/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Active</span></td>
<td class="text-ttb03-mut">4 hrs ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-green tabular-nums">$580</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Selin Novak" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Selin Novak" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="trial" data-name="theo walsh" data-revenue="0">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-violet-500 to-pink-500 text-xs font-semibold text-white">TW</span><div><div class="font-semibold">Theo Walsh</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_5503</div></div></div></td>
<td class="text-ttb03-mut">theo@startup.xyz</td>
<td><span class="rounded-md bg-ttb03-surface2 px-2 py-0.5 text-[11px] font-semibold text-ttb03-mut">Free</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-purple/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-purple ring-1 ring-ttb03-purple/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Trial</span></td>
<td class="text-ttb03-mut">2 days ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-mut tabular-nums">$0</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Theo Walsh" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Theo Walsh" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="banned" data-name="lena kim" data-revenue="-120">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-rose-500 to-rose-300 text-xs font-semibold text-white">LK</span><div><div class="font-semibold">Lena Kim</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_4491</div></div></div></td>
<td class="text-ttb03-mut">lena.kim@corp.io</td>
<td><span class="rounded-md bg-ttb03-surface2 px-2 py-0.5 text-[11px] font-semibold text-ttb03-mut">Free</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-red/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-red ring-1 ring-ttb03-red/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Banned</span></td>
<td class="text-ttb03-mut">14 days ago</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-red tabular-nums">−$120</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit Lena Kim" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for Lena Kim" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-status="active" data-name="james park" data-revenue="12400">
<td><div class="flex items-center gap-2.5"><span aria-hidden="true" class="grid size-8 place-items-center rounded-full bg-linear-135 from-sky-500 to-sky-300 text-xs font-semibold text-white">JP</span><div><div class="font-semibold">James Park</div><div class="font-ttb03-mono text-[11px] text-ttb03-mut">#usr_3380</div></div></div></td>
<td class="text-ttb03-mut">jpark@techco.dev</td>
<td><span class="rounded-md bg-ttb03-purple/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-purple">Enterprise</span></td>
<td><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb03-green/12 px-2 py-0.5 text-[11px] font-semibold text-ttb03-green ring-1 ring-ttb03-green/30"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Active</span></td>
<td class="text-ttb03-mut">Just now</td>
<td class="text-right font-ttb03-mono font-medium text-ttb03-green tabular-nums">$12,400</td>
<td><div class="flex justify-end gap-1"><button type="button" aria-label="Edit James Park" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg></button><button type="button" aria-label="More actions for James Park" class="relative grid size-8 place-items-center rounded-md text-ttb03-mut ring-1 ring-ttb03-line transition after:absolute after:-inset-1.5 hover:text-ttb03-blue hover:ring-ttb03-blue/60 focus-visible:outline-2 focus-visible:outline-ttb03-blue"><svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button></div></td>
</tr>
<tr data-empty hidden><td colspan="7" class="py-12! text-center text-ttb03-mut">No users match this filter.</td></tr>
</tbody>
</table>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-ttb03-bg: #0d1117;
--color-ttb03-bg: oklch(0.18 0.012 260);
--color-ttb03-surface: #161b22;
--color-ttb03-surface: oklch(0.21 0.013 260);
--color-ttb03-surface2: #21262d;
--color-ttb03-surface2: oklch(0.26 0.012 260);
--color-ttb03-line: #30363d;
--color-ttb03-line: oklch(0.34 0.012 260);
--color-ttb03-ink: #e6edf3;
--color-ttb03-ink: oklch(0.94 0.01 240);
--color-ttb03-mut: #9198a1;
--color-ttb03-mut: oklch(0.68 0.015 250);
--color-ttb03-blue: #58a6ff;
--color-ttb03-blue: oklch(0.72 0.15 255);
--color-ttb03-green: #3fb950;
--color-ttb03-green: oklch(0.7 0.17 145);
--color-ttb03-yellow: #d29922;
--color-ttb03-yellow: oklch(0.71 0.14 80);
--color-ttb03-red: #f85149;
--color-ttb03-red: oklch(0.66 0.2 25);
--color-ttb03-purple: #bc8cff;
--color-ttb03-purple: oklch(0.73 0.15 300);
--font-ttb03-sans: 'IBM Plex Sans', system-ui, sans-serif;
--font-ttb03-mono: 'IBM Plex Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.ttb-03 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
const root = document.getElementById('ttb03-grid');
if (!root || root.dataset.wired) return;
root.dataset.wired = '1';
const body = root.querySelector('tbody');
const rows = Array.from(body.querySelectorAll('tr[data-status]'));
const empty = body.querySelector('[data-empty]');
const count = root.querySelector('[data-count]');
const search = root.querySelector('input[type="search"]');
let filter = 'all';
const apply = () => {
const q = search.value.trim().toLowerCase();
let n = 0;
rows.forEach((r) => {
const ok = (filter === 'all' || r.dataset.status === filter) && r.textContent.toLowerCase().includes(q);
r.hidden = !ok;
if (ok) n++;
});
empty.hidden = n !== 0;
count.textContent = n + (n === 1 ? ' user' : ' users');
};
root.querySelectorAll('[data-filter]').forEach((btn) => btn.addEventListener('click', () => {
root.querySelectorAll('[data-filter]').forEach((b) => b.setAttribute('aria-pressed', String(b === btn)));
filter = btn.dataset.filter;
apply();
}));
search.addEventListener('input', apply);
root.querySelectorAll('[data-sort]').forEach((btn) => btn.addEventListener('click', () => {
const th = btn.closest('th');
const dir = th.getAttribute('aria-sort') === 'ascending' ? 'descending' : 'ascending';
root.querySelectorAll('th[aria-sort]').forEach((t) => t.setAttribute('aria-sort', 'none'));
th.setAttribute('aria-sort', dir);
const key = btn.dataset.sort;
const val = (r) => key === 'revenue' ? Number(r.dataset.revenue) : r.dataset.name;
rows.sort((a, b) => {
const x = val(a), y = val(b);
const c = x > y ? 1 : x < y ? -1 : 0;
return dir === 'ascending' ? c : -c;
});
rows.forEach((r) => body.insertBefore(r, empty));
}));
apply();
})();
```How this works
The script never touches a class. Filter pills toggle aria-pressed, sortable headers toggle aria-sort, rows toggle hidden. Every look is a variant of those attributes:
<button aria-pressed="true" class="aria-pressed:bg-ttb03-blue/12
aria-pressed:text-ttb03-blue aria-pressed:ring-ttb03-blue/50">All</button>
<th aria-sort="none" class="group/th">…
<svg class="group-aria-[sort=descending]/th:rotate-180">Sticky cells, not a sticky thead. Each <th> gets sticky top-0 plus an opaque bg-ttb03-surface2, the most reliable spelling across engines. The bottom rule is a shadow-[inset_0_-1px_0_…] because collapsed borders scroll away with the rows.
Sorting re-inserts rows before the empty-state row, so the "No users match" message always stays last.
Make it yours
- Add a sortable column: give its header a
<button data-sort="key">and each row a matchingdata-keyattribute. - Taller window: change
max-h-[22rem]or remove it for a full-page grid. - Frosted header: swap
bg-ttb03-surface2forbg-ttb03-surface2/80 backdrop-bluron the header cells. - New filter: add a pill with
data-filter="trial"; rows withdata-status="trial"are matched automatically.
Gotchas — read before shipping
- Any ancestor with
overflowbetween the scroller and the table breakssticky— keep the scroll region the table's direct parent. - Keep
aria-sorton only one header at a time; the script resets the others tonone. hiddenon a<tr>works because Tailwind's preflight forces[hidden] { display:none !important }.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Sticky table cells are universal in evergreen engines; the floor is Tailwind v4's generated sheet. With JS disabled the grid renders fully, unsorted and unfiltered.