Open demo

CSS code

HTML
<div class="w">
  <div class="tabs" role="radiogroup" aria-label="Filter">
    <input type="radio" name="q" id="q1" checked><label for="q1">All <i>4</i></label>
    <input type="radio" name="q" id="q2"><label for="q2">Open <i>2</i></label>
    <input type="radio" name="q" id="q3"><label for="q3">In review <i>2</i></label>
    <input type="radio" name="q" id="q4"><label for="q4">Archived <i>0</i></label>
  </div>
  <div class="body">
    <div class="p p1">
      <div class="item"><span class="dot" style="background:#f59e0b"></span><span><b>Rework the onboarding checklist</b><small>Open · Rosa M. · due Friday</small></span></div>
      <div class="item"><span class="dot" style="background:#f59e0b"></span><span><b>Audit colour contrast</b><small>Open · Tomas P.</small></span></div>
      <div class="item"><span class="dot" style="background:#0ea5e9"></span><span><b>Ship the CSV export</b><small>In review · Aigars K.</small></span></div>
      <div class="item"><span class="dot" style="background:#0ea5e9"></span><span><b>Migrate the docs site</b><small>In review · Priya N.</small></span></div>
    </div>
    <div class="p p2">
      <div class="item"><span class="dot" style="background:#f59e0b"></span><span><b>Rework the onboarding checklist</b><small>Rosa M. · due Friday</small></span></div>
      <div class="item"><span class="dot" style="background:#f59e0b"></span><span><b>Audit colour contrast</b><small>Tomas P.</small></span></div>
    </div>
    <div class="p p3">
      <div class="item"><span class="dot" style="background:#0ea5e9"></span><span><b>Ship the CSV export</b><small>Aigars K. · 2 approvals</small></span></div>
      <div class="item"><span class="dot" style="background:#0ea5e9"></span><span><b>Migrate the docs site</b><small>Priya N. · waiting on review</small></span></div>
    </div>
    <div class="p p4"><p class="none">Nothing archived yet — the count said so before you clicked.</p></div>
  </div>
</div>
CSS
/* Filter tabs, CSS only: pill-shaped radio labels that carry a count, so an empty filter says so before it is
   clicked. :has() shows the list for the checked radio. */
.w{width:100%;max-width:560px;padding:24px 26px 26px;background:#fff;border:1px solid #e3e8f2;border-radius:16px;
  box-shadow:0 16px 38px -24px rgba(16,24,48,.24)}
.tabs{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:20px;position:relative}
.tabs input{position:absolute;opacity:0;width:1px;height:1px;margin:0;pointer-events:none}
.tabs label{display:inline-flex;align-items:center;gap:8px;padding:8px 14px;border-radius:999px;
  border:1px solid #e3e8f2;font-size:.85rem;font-weight:700;color:#5d6a86;cursor:pointer;
  transition:border-color .15s,background-color .15s,color .15s}
.tabs label:hover{border-color:#c6cede;color:#16203a}
.tabs label i{font-style:normal;font-size:.75rem;background:#f2f4f9;color:#5d6a86;padding:1px 8px;
  border-radius:999px;transition:background-color .15s,color .15s}
.tabs input:checked+label{background:#16203a;border-color:#16203a;color:#fff}
.tabs input:checked+label i{background:rgba(255,255,255,.2);color:#fff}
.tabs input:focus-visible+label{outline:2px solid #4f46e5;outline-offset:2px}
.body .p{display:none}
.w:has(#q1:checked) .p1,.w:has(#q2:checked) .p2,.w:has(#q3:checked) .p3,.w:has(#q4:checked) .p4{display:block}
.item{display:flex;gap:12px;align-items:flex-start;padding:12px 0;border-bottom:1px solid #f2f4f9}
.item:last-child{border-bottom:0}
.item .dot{width:8px;height:8px;border-radius:50%;margin-top:6px;flex-shrink:0}
.item b{display:block;font-size:.88rem}
.item small{font-size:.79rem;color:#5d6a86}
.none{padding:26px 0;text-align:center;font-size:.86rem;color:#5d6a86;margin:0}
@media (prefers-reduced-motion:reduce){.tabs label,.tabs label i{transition:none}}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS, Bootstrap 5.3.8 JS bundle

HTML
<div class="w">
  <div class="nav nav-pills tabs" role="tablist" aria-label="Filter">
    <button class="nav-link active" id="filter-all-tab" data-bs-toggle="pill" data-bs-target="#filter-all" type="button" role="tab" aria-controls="filter-all" aria-selected="true">All <span class="badge rounded-pill">4</span></button>
    <button class="nav-link" id="filter-open-tab" data-bs-toggle="pill" data-bs-target="#filter-open" type="button" role="tab" aria-controls="filter-open" aria-selected="false" tabindex="-1">Open <span class="badge rounded-pill">2</span></button>
    <button class="nav-link" id="filter-review-tab" data-bs-toggle="pill" data-bs-target="#filter-review" type="button" role="tab" aria-controls="filter-review" aria-selected="false" tabindex="-1">In review <span class="badge rounded-pill">2</span></button>
    <button class="nav-link" id="filter-archived-tab" data-bs-toggle="pill" data-bs-target="#filter-archived" type="button" role="tab" aria-controls="filter-archived" aria-selected="false" tabindex="-1">Archived <span class="badge rounded-pill">0</span></button>
  </div>
  <div class="tab-content">
    <div class="tab-pane active" id="filter-all" role="tabpanel" aria-labelledby="filter-all-tab" tabindex="0">
      <div class="item"><span class="dot" style="background:#f59e0b"></span><span><b>Rework the onboarding checklist</b><small>Open · Rosa M. · due Friday</small></span></div>
      <div class="item"><span class="dot" style="background:#f59e0b"></span><span><b>Audit colour contrast</b><small>Open · Tomas P.</small></span></div>
      <div class="item"><span class="dot" style="background:#0ea5e9"></span><span><b>Ship the CSV export</b><small>In review · Aigars K.</small></span></div>
      <div class="item"><span class="dot" style="background:#0ea5e9"></span><span><b>Migrate the docs site</b><small>In review · Priya N.</small></span></div>
    </div>
    <div class="tab-pane" id="filter-open" role="tabpanel" aria-labelledby="filter-open-tab" tabindex="0">
      <div class="item"><span class="dot" style="background:#f59e0b"></span><span><b>Rework the onboarding checklist</b><small>Rosa M. · due Friday</small></span></div>
      <div class="item"><span class="dot" style="background:#f59e0b"></span><span><b>Audit colour contrast</b><small>Tomas P.</small></span></div>
    </div>
    <div class="tab-pane" id="filter-review" role="tabpanel" aria-labelledby="filter-review-tab" tabindex="0">
      <div class="item"><span class="dot" style="background:#0ea5e9"></span><span><b>Ship the CSV export</b><small>Aigars K. · 2 approvals</small></span></div>
      <div class="item"><span class="dot" style="background:#0ea5e9"></span><span><b>Migrate the docs site</b><small>Priya N. · waiting on review</small></span></div>
    </div>
    <div class="tab-pane" id="filter-archived" role="tabpanel" aria-labelledby="filter-archived-tab" tabindex="0"><p class="none">Nothing archived yet — the count said so before you clicked.</p></div>
  </div>
</div>
CSS
/* Filter tabs: Bootstrap .nav-pills with a .badge count in each .nav-link, driven by the Tab plugin,
   so an empty filter says so before it is clicked. */
.w{width:100%;max-width:560px;padding:24px 26px 26px;background:#fff;border:1px solid #e3e8f2;border-radius:16px;
  box-shadow:0 16px 38px -24px rgba(16,24,48,.24)}
.tabs{--bs-nav-link-padding-x:14px;--bs-nav-link-padding-y:8px;--bs-nav-link-font-size:.85rem;--bs-nav-link-font-weight:700;
  --bs-nav-link-color:#5d6a86;--bs-nav-link-hover-color:#16203a;--bs-nav-pills-border-radius:999px;
  --bs-nav-pills-link-active-bg:#16203a;--bs-nav-pills-link-active-color:#fff;gap:6px;margin-bottom:20px}
.tabs .nav-link{display:inline-flex;align-items:center;gap:8px;line-height:normal;border:1px solid #e3e8f2}
.tabs .nav-link:hover{border-color:#c6cede}
.tabs .nav-link.active{border-color:#16203a}
.tabs .nav-link:focus-visible{box-shadow:none;outline:2px solid #4f46e5;outline-offset:2px}
.tabs .badge{--bs-badge-padding-x:8px;--bs-badge-padding-y:1px;--bs-badge-font-size:.75rem;--bs-badge-font-weight:700;
  --bs-badge-color:#5d6a86;line-height:normal;background:#f2f4f9;transition:background-color .15s,color .15s}
.tabs .active .badge{--bs-badge-color:#fff;background:rgba(255,255,255,.2)}
.w .tab-pane:focus-visible{outline:2px solid #4f46e5;outline-offset:4px;border-radius:4px}
.item{display:flex;gap:12px;align-items:flex-start;padding:12px 0;border-bottom:1px solid #f2f4f9}
.item:last-child{border-bottom:0}
.item .dot{width:8px;height:8px;border-radius:50%;margin-top:6px;flex-shrink:0}
.item b{display:block;font-size:.88rem}
.item small{font-size:.79rem;color:#5d6a86}
.none{padding:26px 0;text-align:center;font-size:.86rem;color:#5d6a86;margin:0}
@media (prefers-reduced-motion:reduce){.tabs .badge{transition:none}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div class="w-full max-w-[560px] rounded-2xl border border-tab-line bg-white px-[26px] pt-6 pb-[26px] shadow-[0_16px_38px_-24px_rgba(16,24,48,.24)]">
  <div class="mb-5 flex flex-wrap gap-1.5" role="tablist" aria-label="Filter">
    <button type="button" role="tab" id="filter-all-tab" aria-controls="filter-all" aria-selected="true" class="group inline-flex cursor-pointer items-center gap-2 rounded-full border border-tab-line px-3.5 py-2 text-[.85rem] font-bold text-tab-muted transition-[border-color,background-color,color] duration-150 hover:border-[#c6cede] hover:text-tab-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab-accent aria-selected:border-tab-ink aria-selected:bg-tab-ink aria-selected:text-white motion-reduce:transition-none">All <i class="rounded-full bg-tab-chip px-2 py-px text-[.75rem] not-italic text-tab-muted transition-[background-color,color] duration-150 group-aria-selected:bg-white/20 group-aria-selected:text-white motion-reduce:transition-none">4</i></button>
    <button type="button" role="tab" id="filter-open-tab" aria-controls="filter-open" aria-selected="false" tabindex="-1" class="group inline-flex cursor-pointer items-center gap-2 rounded-full border border-tab-line px-3.5 py-2 text-[.85rem] font-bold text-tab-muted transition-[border-color,background-color,color] duration-150 hover:border-[#c6cede] hover:text-tab-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab-accent aria-selected:border-tab-ink aria-selected:bg-tab-ink aria-selected:text-white motion-reduce:transition-none">Open <i class="rounded-full bg-tab-chip px-2 py-px text-[.75rem] not-italic text-tab-muted transition-[background-color,color] duration-150 group-aria-selected:bg-white/20 group-aria-selected:text-white motion-reduce:transition-none">2</i></button>
    <button type="button" role="tab" id="filter-review-tab" aria-controls="filter-review" aria-selected="false" tabindex="-1" class="group inline-flex cursor-pointer items-center gap-2 rounded-full border border-tab-line px-3.5 py-2 text-[.85rem] font-bold text-tab-muted transition-[border-color,background-color,color] duration-150 hover:border-[#c6cede] hover:text-tab-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab-accent aria-selected:border-tab-ink aria-selected:bg-tab-ink aria-selected:text-white motion-reduce:transition-none">In review <i class="rounded-full bg-tab-chip px-2 py-px text-[.75rem] not-italic text-tab-muted transition-[background-color,color] duration-150 group-aria-selected:bg-white/20 group-aria-selected:text-white motion-reduce:transition-none">2</i></button>
    <button type="button" role="tab" id="filter-archived-tab" aria-controls="filter-archived" aria-selected="false" tabindex="-1" class="group inline-flex cursor-pointer items-center gap-2 rounded-full border border-tab-line px-3.5 py-2 text-[.85rem] font-bold text-tab-muted transition-[border-color,background-color,color] duration-150 hover:border-[#c6cede] hover:text-tab-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab-accent aria-selected:border-tab-ink aria-selected:bg-tab-ink aria-selected:text-white motion-reduce:transition-none">Archived <i class="rounded-full bg-tab-chip px-2 py-px text-[.75rem] not-italic text-tab-muted transition-[background-color,color] duration-150 group-aria-selected:bg-white/20 group-aria-selected:text-white motion-reduce:transition-none">0</i></button>
  </div>
  <div>
    <div id="filter-all" role="tabpanel" aria-labelledby="filter-all-tab" tabindex="0" class="rounded focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tab-accent">
      <div class="flex items-start gap-3 border-b border-tab-chip py-3 last:border-b-0"><span class="mt-1.5 size-2 shrink-0 rounded-full bg-[#f59e0b]"></span><span><b class="block text-[.88rem]">Rework the onboarding checklist</b><small class="text-[.79rem] text-tab-muted">Open · Rosa M. · due Friday</small></span></div>
      <div class="flex items-start gap-3 border-b border-tab-chip py-3 last:border-b-0"><span class="mt-1.5 size-2 shrink-0 rounded-full bg-[#f59e0b]"></span><span><b class="block text-[.88rem]">Audit colour contrast</b><small class="text-[.79rem] text-tab-muted">Open · Tomas P.</small></span></div>
      <div class="flex items-start gap-3 border-b border-tab-chip py-3 last:border-b-0"><span class="mt-1.5 size-2 shrink-0 rounded-full bg-[#0ea5e9]"></span><span><b class="block text-[.88rem]">Ship the CSV export</b><small class="text-[.79rem] text-tab-muted">In review · Aigars K.</small></span></div>
      <div class="flex items-start gap-3 border-b border-tab-chip py-3 last:border-b-0"><span class="mt-1.5 size-2 shrink-0 rounded-full bg-[#0ea5e9]"></span><span><b class="block text-[.88rem]">Migrate the docs site</b><small class="text-[.79rem] text-tab-muted">In review · Priya N.</small></span></div>
    </div>
    <div id="filter-open" role="tabpanel" aria-labelledby="filter-open-tab" tabindex="0" hidden class="rounded focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tab-accent">
      <div class="flex items-start gap-3 border-b border-tab-chip py-3 last:border-b-0"><span class="mt-1.5 size-2 shrink-0 rounded-full bg-[#f59e0b]"></span><span><b class="block text-[.88rem]">Rework the onboarding checklist</b><small class="text-[.79rem] text-tab-muted">Rosa M. · due Friday</small></span></div>
      <div class="flex items-start gap-3 border-b border-tab-chip py-3 last:border-b-0"><span class="mt-1.5 size-2 shrink-0 rounded-full bg-[#f59e0b]"></span><span><b class="block text-[.88rem]">Audit colour contrast</b><small class="text-[.79rem] text-tab-muted">Tomas P.</small></span></div>
    </div>
    <div id="filter-review" role="tabpanel" aria-labelledby="filter-review-tab" tabindex="0" hidden class="rounded focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tab-accent">
      <div class="flex items-start gap-3 border-b border-tab-chip py-3 last:border-b-0"><span class="mt-1.5 size-2 shrink-0 rounded-full bg-[#0ea5e9]"></span><span><b class="block text-[.88rem]">Ship the CSV export</b><small class="text-[.79rem] text-tab-muted">Aigars K. · 2 approvals</small></span></div>
      <div class="flex items-start gap-3 border-b border-tab-chip py-3 last:border-b-0"><span class="mt-1.5 size-2 shrink-0 rounded-full bg-[#0ea5e9]"></span><span><b class="block text-[.88rem]">Migrate the docs site</b><small class="text-[.79rem] text-tab-muted">Priya N. · waiting on review</small></span></div>
    </div>
    <div id="filter-archived" role="tabpanel" aria-labelledby="filter-archived-tab" tabindex="0" hidden class="rounded focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tab-accent">
      <p class="py-[26px] text-center text-[.86rem] text-tab-muted">Nothing archived yet — the count said so before you clicked.</p>
    </div>
  </div>
</div>
CSS
@theme {
  --color-tab-ink: #16203a;
  --color-tab-muted: #5d6a86;
  --color-tab-line: #e3e8f2;
  --color-tab-chip: #f2f4f9;
  --color-tab-accent: #4f46e5;
}
JavaScript
// WAI-ARIA tabs: click or arrow keys select a tab, Home/End jump to the ends, only the selected tab is in the Tab order.
for (const list of document.querySelectorAll('[role=tablist]')) {
  const tabs = [...list.querySelectorAll('[role=tab]')];
  const select = tab => tabs.forEach(t => {
    const on = t === tab;
    t.setAttribute('aria-selected', on);
    t.tabIndex = on ? 0 : -1;
    document.getElementById(t.getAttribute('aria-controls')).hidden = !on;
  });
  list.addEventListener('click', e => { const t = e.target.closest('[role=tab]'); if (t && !t.disabled) select(t); });
  list.addEventListener('keydown', e => {
    const live = tabs.filter(t => !t.disabled), i = live.indexOf(e.target);
    const n = { ArrowLeft: i - 1, ArrowUp: i - 1, ArrowRight: i + 1, ArrowDown: i + 1, Home: 0, End: live.length - 1 }[e.key];
    if (n === undefined || i < 0) return;
    e.preventDefault();
    const t = live[(n + live.length) % live.length];
    t.focus();
    select(t);
  });
}

About this set

Pill-shaped filter tabs that carry a count, All, Open, In review and Archived, above a task list, so people can see how many items each filter holds before clicking and an empty filter says so up front. Use it for issue trackers, inboxes, order lists and any list with a handful of fixed statuses. The selected pill turns dark navy with its count on a translucent white chip, and the others keep a light outline that darkens on hover. Each list row has a coloured status dot, a title and a meta line, and the Archived panel shows an empty-state message. The counts match the rows in each list. The plain CSS version is a radio group that wraps onto a second line on narrow screens, with :has() picking the list. The Bootstrap version is a .nav-pills of rounded .nav-link buttons with a .badge.rounded-pill for each count, driven by the Tab plugin. The Tailwind version styles the count with group-aria-selected utilities and uses a small WAI-ARIA tabs script.

What’s included

  • Pill tabs with a count chip in each
  • Empty filter shows a zero count and an empty-state message
  • Pills wrap onto a second line on narrow screens
  • Bootstrap version uses .nav-pills with .badge counts
  • Count chips and meta text darkened to pass 4.5:1

Accessibility

In the plain CSS version the tabs are a radio group, not ARIA tabs: the radios sit inside an element with role="radiogroup" and an aria-label, each takes its name from its visible label, Tab enters the group and the arrow keys move the selection (radios have no Home or End). In the Bootstrap and Tailwind versions they follow the WAI-ARIA tabs pattern: role tablist, tab and tabpanel, aria-selected, aria-controls, a roving tabindex, arrow keys plus Home and End. Each tab's name includes its count, for example Open 2. Focus shows a 2px indigo outline and the colour transitions are removed under prefers-reduced-motion.

Customise it

Change the selected navy #16203a and the chip colour #f2f4f9 in the .tabs label rules (plain CSS), --bs-nav-pills-link-active-bg and the .badge background (Bootstrap), or --color-tab-ink and --color-tab-chip (Tailwind).