Pure TailwindMIT licensed

Responsive Tailwind Table That Stacks into Cards on Mobile

The most-searched table problem, solved without a media query: a team-member table that collapses into labelled cards when its container is narrow. Column labels are generated with before:content-[attr(data-label)], the header goes sr-only instead of vanishing, and the whole collapse lives on one tbody class list.

Live Demo
Plain CSSTailwind v4
Try it

Mosaic by Cruip: a complete Tailwind admin dashboard template in HTML, React, Next.js and Vue · $69

Cruip · Mosaic by Cruip · Affiliate link

View Mosaic dashboard

The code

<section class="ttb-01 grid-cols-1 w-full min-h-svh grid place-items-center bg-ttb01-bg bg-[radial-gradient(80%_60%_at_50%_0%,var(--color-ttb01-glow),transparent_70%)] px-5 py-14 font-ttb01-sans text-ttb01-ink" aria-label="Responsive stacked table demo">
  <div class="@container/tbl w-full max-w-4xl">
    <header class="mb-6 flex flex-wrap items-center justify-between gap-4">
      <div class="flex items-center gap-3.5">
        <span aria-hidden="true" class="grid size-11 place-items-center rounded-xl bg-linear-135 from-ttb01-acc to-ttb01-acc2 shadow-[0_10px_24px_-8px_var(--color-ttb01-acc)]">
          <svg class="size-5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="9" cy="8" r="3.5"/><path d="M2.5 20a6.5 6.5 0 0 1 13 0"/><path d="M16 4.5a3.5 3.5 0 0 1 0 7M18.5 20a6.5 6.5 0 0 0-3-5.6"/></svg>
        </span>
        <div>
          <h2 class="text-xl font-bold tracking-tight">Team members</h2>
          <p class="mt-0.5 text-sm text-ttb01-mut">4 people · narrow the stage to see rows become cards</p>
        </div>
      </div>
      <button type="button" class="inline-flex min-h-11 items-center gap-2 rounded-xl bg-ttb01-acc px-4 text-sm font-semibold text-white shadow-[0_8px_20px_-8px_var(--color-ttb01-acc)] transition hover:brightness-110 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ttb01-acc2">
        <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>
        Invite member
      </button>
    </header>
    <div class="overflow-hidden rounded-2xl border border-ttb01-line bg-ttb01-card shadow-[0_24px_60px_-24px_oklch(0_0_0/0.75)] @max-[40rem]/tbl:overflow-visible @max-[40rem]/tbl:border-0 @max-[40rem]/tbl:bg-transparent @max-[40rem]/tbl:shadow-none">
      <table class="w-full border-collapse text-left text-sm @max-[40rem]/tbl:block">
        <caption class="sr-only">Team members with role, status and join date</caption>
        <thead class="bg-linear-to-r from-ttb01-acc/15 to-ttb01-acc2/5 @max-[40rem]/tbl:sr-only">
          <tr class="border-b border-ttb01-line [&_th]:px-5 [&_th]:py-3.5 [&_th]:text-[11px] [&_th]:font-bold [&_th]:tracking-[0.14em] [&_th]:text-ttb01-mut [&_th]:uppercase">
            <th scope="col" class="text-ttb01-acc2!">User</th>
            <th scope="col">Role</th>
            <th scope="col">Status</th>
            <th scope="col">Joined</th>
            <th scope="col" class="text-right"><span class="sr-only">Actions</span></th>
          </tr>
        </thead>
        <tbody class="divide-y divide-ttb01-line [&_td]:px-5 [&_td]:py-4 [&_td]:align-middle [&_tr]:transition-[background-color,box-shadow] [&_tr]:duration-200 [&_tr:hover]:bg-ttb01-acc/8 [&_tr:hover]:shadow-[inset_3px_0_0_var(--color-ttb01-acc)] @max-[40rem]/tbl:grid @max-[40rem]/tbl:gap-3 @max-[40rem]/tbl:[&_tr]:block @max-[40rem]/tbl:[&_tr]:overflow-hidden @max-[40rem]/tbl:[&_tr]:rounded-2xl @max-[40rem]/tbl:[&_tr]:border @max-[40rem]/tbl:[&_tr]:border-ttb01-line @max-[40rem]/tbl:[&_tr]:bg-ttb01-card @max-[40rem]/tbl:[&_td]:flex @max-[40rem]/tbl:[&_td]:items-center @max-[40rem]/tbl:[&_td]:justify-between @max-[40rem]/tbl:[&_td]:gap-4 @max-[40rem]/tbl:[&_td]:py-3 @max-[40rem]/tbl:[&_td:not(:last-child)]:border-b @max-[40rem]/tbl:[&_td]:border-ttb01-line @max-[40rem]/tbl:[&_td]:before:content-[attr(data-label)] @max-[40rem]/tbl:[&_td]:before:text-[10.5px] @max-[40rem]/tbl:[&_td]:before:font-bold @max-[40rem]/tbl:[&_td]:before:tracking-[0.14em] @max-[40rem]/tbl:[&_td]:before:text-ttb01-mut @max-[40rem]/tbl:[&_td]:before:uppercase">
          <tr>
            <td data-label="User">
              <div class="flex items-center gap-3">
                <span aria-hidden="true" class="grid size-10 flex-none place-items-center rounded-full bg-linear-135 from-ttb01-acc to-ttb01-acc2 text-[13px] font-bold text-white ring-2 ring-ttb01-card">AK</span>
                <div class="text-left"><div class="font-semibold">Arya Kapoor</div><div class="text-xs text-ttb01-mut">arya@nexuslab.io</div></div>
              </div>
            </td>
            <td data-label="Role"><span class="rounded-md border border-ttb01-acc/30 bg-ttb01-acc/12 px-2 py-1 text-xs font-medium text-ttb01-acc-ink">Admin</span></td>
            <td data-label="Status"><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb01-ok/12 px-2.5 py-1 text-xs font-semibold text-ttb01-ok"><span aria-hidden="true" class="size-1.5 rounded-full bg-current animate-ttb01-pulse motion-reduce:animate-none"></span>Active</span></td>
            <td data-label="Joined" class="text-[13px] text-ttb01-mut tabular-nums"><time datetime="2025-01-12">Jan 12, 2025</time></td>
            <td data-label="Actions">
              <div class="flex justify-end gap-1.5">
                <button type="button" aria-label="Edit Arya Kapoor" class="grid size-11 place-items-center rounded-xl text-ttb01-mut transition hover:bg-ttb01-acc/15 hover:text-ttb01-acc-ink focus-visible:outline-2 focus-visible:outline-ttb01-acc2"><svg class="size-4.5" 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"/><path d="M13.5 6.5l4 4"/></svg></button>
                <button type="button" aria-label="Remove Arya Kapoor" class="grid size-11 place-items-center rounded-xl text-ttb01-mut transition hover:bg-ttb01-bad/15 hover:text-ttb01-bad focus-visible:outline-2 focus-visible:outline-ttb01-bad"><svg class="size-4.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/></svg></button>
              </div>
            </td>
          </tr>
          <tr>
            <td data-label="User">
              <div class="flex items-center gap-3">
                <span aria-hidden="true" class="grid size-10 flex-none place-items-center rounded-full bg-linear-135 from-rose-400 to-orange-300 text-[13px] font-bold text-white ring-2 ring-ttb01-card">MR</span>
                <div class="text-left"><div class="font-semibold">Marcus Reid</div><div class="text-xs text-ttb01-mut">m.reid@nexuslab.io</div></div>
              </div>
            </td>
            <td data-label="Role"><span class="rounded-md border border-ttb01-acc/30 bg-ttb01-acc/12 px-2 py-1 text-xs font-medium text-ttb01-acc-ink">Developer</span></td>
            <td data-label="Status"><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb01-warn/12 px-2.5 py-1 text-xs font-semibold text-ttb01-warn"><span aria-hidden="true" class="size-1.5 rounded-full bg-current animate-ttb01-pulse motion-reduce:animate-none"></span>Pending</span></td>
            <td data-label="Joined" class="text-[13px] text-ttb01-mut tabular-nums"><time datetime="2025-03-04">Mar 4, 2025</time></td>
            <td data-label="Actions">
              <div class="flex justify-end gap-1.5">
                <button type="button" aria-label="Edit Marcus Reid" class="grid size-11 place-items-center rounded-xl text-ttb01-mut transition hover:bg-ttb01-acc/15 hover:text-ttb01-acc-ink focus-visible:outline-2 focus-visible:outline-ttb01-acc2"><svg class="size-4.5" 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"/><path d="M13.5 6.5l4 4"/></svg></button>
                <button type="button" aria-label="Remove Marcus Reid" class="grid size-11 place-items-center rounded-xl text-ttb01-mut transition hover:bg-ttb01-bad/15 hover:text-ttb01-bad focus-visible:outline-2 focus-visible:outline-ttb01-bad"><svg class="size-4.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/></svg></button>
              </div>
            </td>
          </tr>
          <tr>
            <td data-label="User">
              <div class="flex items-center gap-3">
                <span aria-hidden="true" class="grid size-10 flex-none place-items-center rounded-full bg-linear-135 from-emerald-400 to-cyan-500 text-[13px] font-bold text-white ring-2 ring-ttb01-card">SN</span>
                <div class="text-left"><div class="font-semibold">Selin Novak</div><div class="text-xs text-ttb01-mut">selin@nexuslab.io</div></div>
              </div>
            </td>
            <td data-label="Role"><span class="rounded-md border border-ttb01-acc/30 bg-ttb01-acc/12 px-2 py-1 text-xs font-medium text-ttb01-acc-ink">Designer</span></td>
            <td data-label="Status"><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb01-ok/12 px-2.5 py-1 text-xs font-semibold text-ttb01-ok"><span aria-hidden="true" class="size-1.5 rounded-full bg-current animate-ttb01-pulse motion-reduce:animate-none"></span>Active</span></td>
            <td data-label="Joined" class="text-[13px] text-ttb01-mut tabular-nums"><time datetime="2025-05-19">May 19, 2025</time></td>
            <td data-label="Actions">
              <div class="flex justify-end gap-1.5">
                <button type="button" aria-label="Edit Selin Novak" class="grid size-11 place-items-center rounded-xl text-ttb01-mut transition hover:bg-ttb01-acc/15 hover:text-ttb01-acc-ink focus-visible:outline-2 focus-visible:outline-ttb01-acc2"><svg class="size-4.5" 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"/><path d="M13.5 6.5l4 4"/></svg></button>
                <button type="button" aria-label="Remove Selin Novak" class="grid size-11 place-items-center rounded-xl text-ttb01-mut transition hover:bg-ttb01-bad/15 hover:text-ttb01-bad focus-visible:outline-2 focus-visible:outline-ttb01-bad"><svg class="size-4.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/></svg></button>
              </div>
            </td>
          </tr>
          <tr>
            <td data-label="User">
              <div class="flex items-center gap-3">
                <span aria-hidden="true" class="grid size-10 flex-none place-items-center rounded-full bg-linear-135 from-red-400 to-violet-500 text-[13px] font-bold text-white ring-2 ring-ttb01-card">TW</span>
                <div class="text-left"><div class="font-semibold">Theo Walsh</div><div class="text-xs text-ttb01-mut">theo@nexuslab.io</div></div>
              </div>
            </td>
            <td data-label="Role"><span class="rounded-md border border-ttb01-acc/30 bg-ttb01-acc/12 px-2 py-1 text-xs font-medium text-ttb01-acc-ink">Manager</span></td>
            <td data-label="Status"><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb01-bad/12 px-2.5 py-1 text-xs font-semibold text-ttb01-bad"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Inactive</span></td>
            <td data-label="Joined" class="text-[13px] text-ttb01-mut tabular-nums"><time datetime="2024-11-02">Nov 2, 2024</time></td>
            <td data-label="Actions">
              <div class="flex justify-end gap-1.5">
                <button type="button" aria-label="Edit Theo Walsh" class="grid size-11 place-items-center rounded-xl text-ttb01-mut transition hover:bg-ttb01-acc/15 hover:text-ttb01-acc-ink focus-visible:outline-2 focus-visible:outline-ttb01-acc2"><svg class="size-4.5" 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"/><path d="M13.5 6.5l4 4"/></svg></button>
                <button type="button" aria-label="Remove Theo Walsh" class="grid size-11 place-items-center rounded-xl text-ttb01-mut transition hover:bg-ttb01-bad/15 hover:text-ttb01-bad focus-visible:outline-2 focus-visible:outline-ttb01-bad"><svg class="size-4.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/></svg></button>
              </div>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-ttb01-bg: #0e0f1a;
  --color-ttb01-bg: oklch(0.17 0.025 280);
  --color-ttb01-glow: #252554;
  --color-ttb01-glow: oklch(0.28 0.09 280 / 0.7);
  --color-ttb01-card: #181a2b;
  --color-ttb01-card: oklch(0.22 0.03 280);
  --color-ttb01-line: #2a2c46;
  --color-ttb01-line: oklch(0.3 0.045 280);
  --color-ttb01-ink: #e7e7f5;
  --color-ttb01-ink: oklch(0.93 0.015 285);
  --color-ttb01-mut: #a4a7c6;
  --color-ttb01-mut: oklch(0.74 0.04 280);
  --color-ttb01-acc: #7a6fff;
  --color-ttb01-acc: oklch(0.62 0.2 282);
  --color-ttb01-acc-ink: #b9b3ff;
  --color-ttb01-acc-ink: oklch(0.8 0.1 285);
  --color-ttb01-acc2: #22d3ee;
  --color-ttb01-acc2: oklch(0.8 0.13 210);
  --color-ttb01-ok: #34d399;
  --color-ttb01-ok: oklch(0.78 0.15 165);
  --color-ttb01-warn: #fbbf24;
  --color-ttb01-warn: oklch(0.84 0.16 85);
  --color-ttb01-bad: #f87171;
  --color-ttb01-bad: oklch(0.71 0.17 22);
  --font-ttb01-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --animate-ttb01-pulse: ttb01-pulse 2s ease-in-out infinite;

  @keyframes ttb01-pulse {
    0%,
        100% {
      opacity: 1;
      transform: scale(1);
    }

    50% {
      opacity: 0.45;
      transform: scale(0.7);
    }
  }
}
/* 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-01 {
  width: 100%;
  min-height: 100svh;
}

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-ttb01-bg: #0e0f1a;
  --color-ttb01-bg: oklch(0.17 0.025 280);
  --color-ttb01-glow: #252554;
  --color-ttb01-glow: oklch(0.28 0.09 280 / 0.7);
  --color-ttb01-card: #181a2b;
  --color-ttb01-card: oklch(0.22 0.03 280);
  --color-ttb01-line: #2a2c46;
  --color-ttb01-line: oklch(0.3 0.045 280);
  --color-ttb01-ink: #e7e7f5;
  --color-ttb01-ink: oklch(0.93 0.015 285);
  --color-ttb01-mut: #a4a7c6;
  --color-ttb01-mut: oklch(0.74 0.04 280);
  --color-ttb01-acc: #7a6fff;
  --color-ttb01-acc: oklch(0.62 0.2 282);
  --color-ttb01-acc-ink: #b9b3ff;
  --color-ttb01-acc-ink: oklch(0.8 0.1 285);
  --color-ttb01-acc2: #22d3ee;
  --color-ttb01-acc2: oklch(0.8 0.13 210);
  --color-ttb01-ok: #34d399;
  --color-ttb01-ok: oklch(0.78 0.15 165);
  --color-ttb01-warn: #fbbf24;
  --color-ttb01-warn: oklch(0.84 0.16 85);
  --color-ttb01-bad: #f87171;
  --color-ttb01-bad: oklch(0.71 0.17 22);
  --font-ttb01-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --animate-ttb01-pulse: ttb01-pulse 2s ease-in-out infinite;

  @keyframes ttb01-pulse {
    0%,
        100% {
      opacity: 1;
      transform: scale(1);
    }

    50% {
      opacity: 0.45;
      transform: scale(0.7);
    }
  }
}
/* 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-01 {
  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=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap">

What changed in the port

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

  • The original's twelve-line @media (max-width:640px) block became @max-[40rem]/tbl: variants on one tbody — a container query, so the collapse now triggers inside narrow playgrounds, sidebars and modals, not only on phones.
  • td::before { content: attr(data-label) } is before:content-[attr(data-label)], applied to every cell at once through [&_td]:before:.
  • UI upgrade from the CSS original: thead { display:none } became sr-only, so screen-reader users keep the column headers; the hover translateX(3px) that nudged the whole row (and its text) became a layout-stable inset accent rail.
  • UI upgrade: emoji icon and "Edit / Del" text buttons replaced with aria-labelled 44px icon buttons, join dates are real <time> elements, muted copy raised from #888ab0 to 7.4:1, and an Invite member primary action anchors the header.
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: Responsive Tailwind Table That Stacks into Cards on Mobile
Source: https://codefronts.com/snippets/tailwind-css-tables/responsive-tailwind-table-stacks-into-cards-on-mobile/

The most-searched table problem, solved without a media query: a team-member table that collapses into labelled cards when its container is narrow. Column labels are generated with before:content-[attr(data-label)], the header goes sr-only instead of vanishing, and the whole collapse lives on one tbody class list.
## HTML
```html
<section class="ttb-01 grid-cols-1 w-full min-h-svh grid place-items-center bg-ttb01-bg bg-[radial-gradient(80%_60%_at_50%_0%,var(--color-ttb01-glow),transparent_70%)] px-5 py-14 font-ttb01-sans text-ttb01-ink" aria-label="Responsive stacked table demo">
  <div class="@container/tbl w-full max-w-4xl">
    <header class="mb-6 flex flex-wrap items-center justify-between gap-4">
      <div class="flex items-center gap-3.5">
        <span aria-hidden="true" class="grid size-11 place-items-center rounded-xl bg-linear-135 from-ttb01-acc to-ttb01-acc2 shadow-[0_10px_24px_-8px_var(--color-ttb01-acc)]">
          <svg class="size-5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="9" cy="8" r="3.5"/><path d="M2.5 20a6.5 6.5 0 0 1 13 0"/><path d="M16 4.5a3.5 3.5 0 0 1 0 7M18.5 20a6.5 6.5 0 0 0-3-5.6"/></svg>
        </span>
        <div>
          <h2 class="text-xl font-bold tracking-tight">Team members</h2>
          <p class="mt-0.5 text-sm text-ttb01-mut">4 people · narrow the stage to see rows become cards</p>
        </div>
      </div>
      <button type="button" class="inline-flex min-h-11 items-center gap-2 rounded-xl bg-ttb01-acc px-4 text-sm font-semibold text-white shadow-[0_8px_20px_-8px_var(--color-ttb01-acc)] transition hover:brightness-110 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ttb01-acc2">
        <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>
        Invite member
      </button>
    </header>
    <div class="overflow-hidden rounded-2xl border border-ttb01-line bg-ttb01-card shadow-[0_24px_60px_-24px_oklch(0_0_0/0.75)] @max-[40rem]/tbl:overflow-visible @max-[40rem]/tbl:border-0 @max-[40rem]/tbl:bg-transparent @max-[40rem]/tbl:shadow-none">
      <table class="w-full border-collapse text-left text-sm @max-[40rem]/tbl:block">
        <caption class="sr-only">Team members with role, status and join date</caption>
        <thead class="bg-linear-to-r from-ttb01-acc/15 to-ttb01-acc2/5 @max-[40rem]/tbl:sr-only">
          <tr class="border-b border-ttb01-line [&_th]:px-5 [&_th]:py-3.5 [&_th]:text-[11px] [&_th]:font-bold [&_th]:tracking-[0.14em] [&_th]:text-ttb01-mut [&_th]:uppercase">
            <th scope="col" class="text-ttb01-acc2!">User</th>
            <th scope="col">Role</th>
            <th scope="col">Status</th>
            <th scope="col">Joined</th>
            <th scope="col" class="text-right"><span class="sr-only">Actions</span></th>
          </tr>
        </thead>
        <tbody class="divide-y divide-ttb01-line [&_td]:px-5 [&_td]:py-4 [&_td]:align-middle [&_tr]:transition-[background-color,box-shadow] [&_tr]:duration-200 [&_tr:hover]:bg-ttb01-acc/8 [&_tr:hover]:shadow-[inset_3px_0_0_var(--color-ttb01-acc)] @max-[40rem]/tbl:grid @max-[40rem]/tbl:gap-3 @max-[40rem]/tbl:[&_tr]:block @max-[40rem]/tbl:[&_tr]:overflow-hidden @max-[40rem]/tbl:[&_tr]:rounded-2xl @max-[40rem]/tbl:[&_tr]:border @max-[40rem]/tbl:[&_tr]:border-ttb01-line @max-[40rem]/tbl:[&_tr]:bg-ttb01-card @max-[40rem]/tbl:[&_td]:flex @max-[40rem]/tbl:[&_td]:items-center @max-[40rem]/tbl:[&_td]:justify-between @max-[40rem]/tbl:[&_td]:gap-4 @max-[40rem]/tbl:[&_td]:py-3 @max-[40rem]/tbl:[&_td:not(:last-child)]:border-b @max-[40rem]/tbl:[&_td]:border-ttb01-line @max-[40rem]/tbl:[&_td]:before:content-[attr(data-label)] @max-[40rem]/tbl:[&_td]:before:text-[10.5px] @max-[40rem]/tbl:[&_td]:before:font-bold @max-[40rem]/tbl:[&_td]:before:tracking-[0.14em] @max-[40rem]/tbl:[&_td]:before:text-ttb01-mut @max-[40rem]/tbl:[&_td]:before:uppercase">
          <tr>
            <td data-label="User">
              <div class="flex items-center gap-3">
                <span aria-hidden="true" class="grid size-10 flex-none place-items-center rounded-full bg-linear-135 from-ttb01-acc to-ttb01-acc2 text-[13px] font-bold text-white ring-2 ring-ttb01-card">AK</span>
                <div class="text-left"><div class="font-semibold">Arya Kapoor</div><div class="text-xs text-ttb01-mut">arya@nexuslab.io</div></div>
              </div>
            </td>
            <td data-label="Role"><span class="rounded-md border border-ttb01-acc/30 bg-ttb01-acc/12 px-2 py-1 text-xs font-medium text-ttb01-acc-ink">Admin</span></td>
            <td data-label="Status"><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb01-ok/12 px-2.5 py-1 text-xs font-semibold text-ttb01-ok"><span aria-hidden="true" class="size-1.5 rounded-full bg-current animate-ttb01-pulse motion-reduce:animate-none"></span>Active</span></td>
            <td data-label="Joined" class="text-[13px] text-ttb01-mut tabular-nums"><time datetime="2025-01-12">Jan 12, 2025</time></td>
            <td data-label="Actions">
              <div class="flex justify-end gap-1.5">
                <button type="button" aria-label="Edit Arya Kapoor" class="grid size-11 place-items-center rounded-xl text-ttb01-mut transition hover:bg-ttb01-acc/15 hover:text-ttb01-acc-ink focus-visible:outline-2 focus-visible:outline-ttb01-acc2"><svg class="size-4.5" 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"/><path d="M13.5 6.5l4 4"/></svg></button>
                <button type="button" aria-label="Remove Arya Kapoor" class="grid size-11 place-items-center rounded-xl text-ttb01-mut transition hover:bg-ttb01-bad/15 hover:text-ttb01-bad focus-visible:outline-2 focus-visible:outline-ttb01-bad"><svg class="size-4.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/></svg></button>
              </div>
            </td>
          </tr>
          <tr>
            <td data-label="User">
              <div class="flex items-center gap-3">
                <span aria-hidden="true" class="grid size-10 flex-none place-items-center rounded-full bg-linear-135 from-rose-400 to-orange-300 text-[13px] font-bold text-white ring-2 ring-ttb01-card">MR</span>
                <div class="text-left"><div class="font-semibold">Marcus Reid</div><div class="text-xs text-ttb01-mut">m.reid@nexuslab.io</div></div>
              </div>
            </td>
            <td data-label="Role"><span class="rounded-md border border-ttb01-acc/30 bg-ttb01-acc/12 px-2 py-1 text-xs font-medium text-ttb01-acc-ink">Developer</span></td>
            <td data-label="Status"><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb01-warn/12 px-2.5 py-1 text-xs font-semibold text-ttb01-warn"><span aria-hidden="true" class="size-1.5 rounded-full bg-current animate-ttb01-pulse motion-reduce:animate-none"></span>Pending</span></td>
            <td data-label="Joined" class="text-[13px] text-ttb01-mut tabular-nums"><time datetime="2025-03-04">Mar 4, 2025</time></td>
            <td data-label="Actions">
              <div class="flex justify-end gap-1.5">
                <button type="button" aria-label="Edit Marcus Reid" class="grid size-11 place-items-center rounded-xl text-ttb01-mut transition hover:bg-ttb01-acc/15 hover:text-ttb01-acc-ink focus-visible:outline-2 focus-visible:outline-ttb01-acc2"><svg class="size-4.5" 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"/><path d="M13.5 6.5l4 4"/></svg></button>
                <button type="button" aria-label="Remove Marcus Reid" class="grid size-11 place-items-center rounded-xl text-ttb01-mut transition hover:bg-ttb01-bad/15 hover:text-ttb01-bad focus-visible:outline-2 focus-visible:outline-ttb01-bad"><svg class="size-4.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/></svg></button>
              </div>
            </td>
          </tr>
          <tr>
            <td data-label="User">
              <div class="flex items-center gap-3">
                <span aria-hidden="true" class="grid size-10 flex-none place-items-center rounded-full bg-linear-135 from-emerald-400 to-cyan-500 text-[13px] font-bold text-white ring-2 ring-ttb01-card">SN</span>
                <div class="text-left"><div class="font-semibold">Selin Novak</div><div class="text-xs text-ttb01-mut">selin@nexuslab.io</div></div>
              </div>
            </td>
            <td data-label="Role"><span class="rounded-md border border-ttb01-acc/30 bg-ttb01-acc/12 px-2 py-1 text-xs font-medium text-ttb01-acc-ink">Designer</span></td>
            <td data-label="Status"><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb01-ok/12 px-2.5 py-1 text-xs font-semibold text-ttb01-ok"><span aria-hidden="true" class="size-1.5 rounded-full bg-current animate-ttb01-pulse motion-reduce:animate-none"></span>Active</span></td>
            <td data-label="Joined" class="text-[13px] text-ttb01-mut tabular-nums"><time datetime="2025-05-19">May 19, 2025</time></td>
            <td data-label="Actions">
              <div class="flex justify-end gap-1.5">
                <button type="button" aria-label="Edit Selin Novak" class="grid size-11 place-items-center rounded-xl text-ttb01-mut transition hover:bg-ttb01-acc/15 hover:text-ttb01-acc-ink focus-visible:outline-2 focus-visible:outline-ttb01-acc2"><svg class="size-4.5" 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"/><path d="M13.5 6.5l4 4"/></svg></button>
                <button type="button" aria-label="Remove Selin Novak" class="grid size-11 place-items-center rounded-xl text-ttb01-mut transition hover:bg-ttb01-bad/15 hover:text-ttb01-bad focus-visible:outline-2 focus-visible:outline-ttb01-bad"><svg class="size-4.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/></svg></button>
              </div>
            </td>
          </tr>
          <tr>
            <td data-label="User">
              <div class="flex items-center gap-3">
                <span aria-hidden="true" class="grid size-10 flex-none place-items-center rounded-full bg-linear-135 from-red-400 to-violet-500 text-[13px] font-bold text-white ring-2 ring-ttb01-card">TW</span>
                <div class="text-left"><div class="font-semibold">Theo Walsh</div><div class="text-xs text-ttb01-mut">theo@nexuslab.io</div></div>
              </div>
            </td>
            <td data-label="Role"><span class="rounded-md border border-ttb01-acc/30 bg-ttb01-acc/12 px-2 py-1 text-xs font-medium text-ttb01-acc-ink">Manager</span></td>
            <td data-label="Status"><span class="inline-flex items-center gap-1.5 rounded-full bg-ttb01-bad/12 px-2.5 py-1 text-xs font-semibold text-ttb01-bad"><span aria-hidden="true" class="size-1.5 rounded-full bg-current"></span>Inactive</span></td>
            <td data-label="Joined" class="text-[13px] text-ttb01-mut tabular-nums"><time datetime="2024-11-02">Nov 2, 2024</time></td>
            <td data-label="Actions">
              <div class="flex justify-end gap-1.5">
                <button type="button" aria-label="Edit Theo Walsh" class="grid size-11 place-items-center rounded-xl text-ttb01-mut transition hover:bg-ttb01-acc/15 hover:text-ttb01-acc-ink focus-visible:outline-2 focus-visible:outline-ttb01-acc2"><svg class="size-4.5" 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"/><path d="M13.5 6.5l4 4"/></svg></button>
                <button type="button" aria-label="Remove Theo Walsh" class="grid size-11 place-items-center rounded-xl text-ttb01-mut transition hover:bg-ttb01-bad/15 hover:text-ttb01-bad focus-visible:outline-2 focus-visible:outline-ttb01-bad"><svg class="size-4.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/></svg></button>
              </div>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-ttb01-bg: #0e0f1a;
  --color-ttb01-bg: oklch(0.17 0.025 280);
  --color-ttb01-glow: #252554;
  --color-ttb01-glow: oklch(0.28 0.09 280 / 0.7);
  --color-ttb01-card: #181a2b;
  --color-ttb01-card: oklch(0.22 0.03 280);
  --color-ttb01-line: #2a2c46;
  --color-ttb01-line: oklch(0.3 0.045 280);
  --color-ttb01-ink: #e7e7f5;
  --color-ttb01-ink: oklch(0.93 0.015 285);
  --color-ttb01-mut: #a4a7c6;
  --color-ttb01-mut: oklch(0.74 0.04 280);
  --color-ttb01-acc: #7a6fff;
  --color-ttb01-acc: oklch(0.62 0.2 282);
  --color-ttb01-acc-ink: #b9b3ff;
  --color-ttb01-acc-ink: oklch(0.8 0.1 285);
  --color-ttb01-acc2: #22d3ee;
  --color-ttb01-acc2: oklch(0.8 0.13 210);
  --color-ttb01-ok: #34d399;
  --color-ttb01-ok: oklch(0.78 0.15 165);
  --color-ttb01-warn: #fbbf24;
  --color-ttb01-warn: oklch(0.84 0.16 85);
  --color-ttb01-bad: #f87171;
  --color-ttb01-bad: oklch(0.71 0.17 22);
  --font-ttb01-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --animate-ttb01-pulse: ttb01-pulse 2s ease-in-out infinite;

  @keyframes ttb01-pulse {
    0%,
        100% {
      opacity: 1;
      transform: scale(1);
    }

    50% {
      opacity: 0.45;
      transform: scale(0.7);
    }
  }
}
/* 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-01 {
  width: 100%;
  min-height: 100svh;
}
```

How this works

One container, one breakpoint. The wrapper is @container/tbl; every collapse rule is prefixed @max-[40rem]/tbl:, so the table re-stacks based on the space it is given rather than the viewport:

<tbody class="@max-[40rem]/tbl:grid @max-[40rem]/tbl:gap-3
  @max-[40rem]/tbl:[&_tr]:block @max-[40rem]/tbl:[&_td]:flex
  @max-[40rem]/tbl:[&_td]:before:content-[attr(data-label)]">

Descendant variants keep the markup readable. Instead of repeating twenty utilities on every <td>, the rules sit once on tbody through [&_td]: and [&_tr]:. Stacked variants apply left to right: the container query wraps the descendant selector, which wraps ::before.

Desktop dividers use divide-y, which emits a zero-specificity :where() selector — so the card-mode [&_tr]:border wins cleanly without !important.

Make it yours

  • Change the breakpoint: replace every @max-[40rem]/tbl: with @max-[48rem]/tbl: to collapse at tablet widths.
  • Re-theme: edit --color-ttb01-acc and --color-ttb01-acc2 in @theme — the header wash, avatars, role chips and rail all follow.
  • New status: copy a badge and swap bg-ttb01-ok/12 text-ttb01-ok for any palette colour, e.g. bg-sky-400/12 text-sky-300.
  • Wider labels: add @max-[40rem]/tbl:[&_td]:before:min-w-24 to align values in a column.

Gotchas — read before shipping

  • display:block on table parts drops colspan/rowspan in card mode — keep collapsible rows to single-span cells.
  • Some screen readers drop table semantics once rows are display:block; the visible data-label text keeps each value self-describing.
  • data-label must be kept in sync with the <th> text by hand — generate both from one source in a component.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

Size container queries are Chrome 105 / Safari 16 / Firefox 110; attr() in content is universal. The floor is Tailwind v4's generated sheet.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Search CodeFronts

Loading…