Open demo

CSS code

HTML
<div class="dtabs">
  <div class="top"><h2>Deployments</h2></div>
  <div role="tablist" aria-label="Deployment status">
    <button type="button" role="tab" id="dt-tab-0" aria-selected="true" aria-controls="dt-panel-0">Active <span class="n">3</span></button>
    <button type="button" role="tab" id="dt-tab-1" aria-selected="false" aria-controls="dt-panel-1" tabindex="-1">Queued <span class="n">1</span></button>
    <button type="button" role="tab" id="dt-tab-2" aria-selected="false" aria-controls="dt-panel-2" tabindex="-1">Failed <span class="n">2</span></button>
  </div>
  <div role="tabpanel" id="dt-panel-0" aria-labelledby="dt-tab-0" tabindex="0">
    <div class="row">api-gateway · v2.14.1 <span>2m ago</span></div>
    <div class="row">web-frontend · v8.0.3 <span>18m ago</span></div>
    <div class="row">billing-worker · v1.9.0 <span>1h ago</span></div>
  </div>
  <div role="tabpanel" id="dt-panel-1" aria-labelledby="dt-tab-1" tabindex="0" hidden><div class="row">search-index · v3.2.0 <span>waiting for approval</span></div></div>
  <div role="tabpanel" id="dt-panel-2" aria-labelledby="dt-tab-2" tabindex="0" hidden>
    <div class="row">media-transcode · v0.9.1 <span>OOM at build</span></div>
    <div class="row">cron-runner · v2.0.0 <span>tests failed</span></div>
  </div>
</div>
CSS
/* Dark app tabs: underline tabs with count pills, for dashboards and deploy consoles. */
.dtabs{--dt-accent:#22d3ee;--dt-muted:#77839e;--dt-line:#1f2739;--dt-pill:#1c2537;
  box-sizing:border-box;width:100%;max-width:580px;background:#111623;border:1px solid var(--dt-line);border-radius:16px;overflow:hidden;color:#e5eaf4}
.dtabs .top{display:flex;align-items:center;justify-content:space-between;padding:16px 20px 0}
.dtabs .top h2{margin:0;font-size:.98rem;font-weight:700}
.dtabs [role=tablist]{display:flex;gap:18px;padding:10px 20px 0;border-bottom:1px solid var(--dt-line)}
.dtabs [role=tab]{display:flex;gap:7px;align-items:center;margin:0 0 -1px;border:0;border-bottom:2px solid transparent;background:none;padding:10px 2px 12px;
  font:inherit;font-size:.86rem;font-weight:600;color:var(--dt-muted);cursor:pointer;border-radius:4px 4px 0 0;transition:color .15s,border-color .15s}
.dtabs [role=tab]:hover{color:#c6cede}
.dtabs [role=tab][aria-selected=true]{color:#fff;border-bottom-color:var(--dt-accent)}
.dtabs .n{background:var(--dt-pill);color:#8ea0c0;font-size:.68rem;font-weight:800;padding:2px 8px;border-radius:99px}
.dtabs [role=tab][aria-selected=true] .n{background:#0c3a44;color:var(--dt-accent)}
.dtabs [role=tabpanel]{padding:20px;font-size:.86rem;color:#96a2bd;line-height:1.7}
.dtabs .row{display:flex;justify-content:space-between;gap:12px;padding:11px 12px;border:1px solid var(--dt-pill);border-radius:10px;margin-bottom:8px;color:#c6cede}
.dtabs .row span{color:#8190b0;font-size:.78rem;text-align:right}
.dtabs [role=tab]:focus-visible{outline:2px solid var(--dt-accent);outline-offset:2px}
.dtabs [role=tabpanel]:focus-visible{outline:2px solid var(--dt-accent);outline-offset:-6px;border-radius:0 0 16px 16px}
.dtabs [hidden]{display:none}
@media (prefers-reduced-motion:reduce){.dtabs [role=tab]{transition:none}}
JavaScript
/* WAI-ARIA tabs: click or use the arrow keys to select, Home/End jump to the first/last tab.
   Only the selected tab is in the Tab order (roving tabindex). */
document.querySelectorAll('.dtabs').forEach(root => {
  const list = root.querySelector('[role=tablist]');
  const tabs = [...list.querySelectorAll('[role=tab]')];
  const select = (tab, focus) => {
    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;
    });
    if (focus) tab.focus();
  };
  list.addEventListener('click', e => { const t = e.target.closest('[role=tab]'); if (t) select(t); });
  list.addEventListener('keydown', e => {
    const i = tabs.indexOf(e.target), n = tabs.length;
    const j = { ArrowRight: i + 1, ArrowDown: i + 1, ArrowLeft: i - 1 + n, ArrowUp: i - 1 + n, Home: 0, End: n - 1 }[e.key];
    if (i < 0 || j === undefined) return;
    e.preventDefault();
    select(tabs[j % n], true);
  });
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS, Bootstrap 5.3.8 JS bundle

HTML
<div class="dtabs">
  <div class="top"><h2>Deployments</h2></div>
  <ul class="nav nav-underline" role="tablist" aria-label="Deployment status">
    <li class="nav-item" role="presentation"><button class="nav-link active" type="button" id="dt-tab-0" data-bs-toggle="tab" data-bs-target="#dt-panel-0" role="tab" aria-controls="dt-panel-0" aria-selected="true">Active <span class="badge rounded-pill">3</span></button></li>
    <li class="nav-item" role="presentation"><button class="nav-link" type="button" id="dt-tab-1" data-bs-toggle="tab" data-bs-target="#dt-panel-1" role="tab" aria-controls="dt-panel-1" aria-selected="false" tabindex="-1">Queued <span class="badge rounded-pill">1</span></button></li>
    <li class="nav-item" role="presentation"><button class="nav-link" type="button" id="dt-tab-2" data-bs-toggle="tab" data-bs-target="#dt-panel-2" role="tab" aria-controls="dt-panel-2" aria-selected="false" tabindex="-1">Failed <span class="badge rounded-pill">2</span></button></li>
  </ul>
  <div class="tab-content">
    <div class="tab-pane active" id="dt-panel-0" role="tabpanel" aria-labelledby="dt-tab-0" tabindex="0">
      <div class="row-item">api-gateway · v2.14.1 <span>2m ago</span></div>
      <div class="row-item">web-frontend · v8.0.3 <span>18m ago</span></div>
      <div class="row-item">billing-worker · v1.9.0 <span>1h ago</span></div>
    </div>
    <div class="tab-pane" id="dt-panel-1" role="tabpanel" aria-labelledby="dt-tab-1" tabindex="0"><div class="row-item">search-index · v3.2.0 <span>waiting for approval</span></div></div>
    <div class="tab-pane" id="dt-panel-2" role="tabpanel" aria-labelledby="dt-tab-2" tabindex="0">
      <div class="row-item">media-transcode · v0.9.1 <span>OOM at build</span></div>
      <div class="row-item">cron-runner · v2.0.0 <span>tests failed</span></div>
    </div>
  </div>
</div>
CSS
/* Bootstrap .nav-underline with the Tab plugin and .badge counts, restyled through the --bs-nav-* and --bs-badge-* variables. */
.dtabs{--dt-accent:#22d3ee;--dt-muted:#77839e;--dt-line:#1f2739;--dt-pill:#1c2537;
  width:100%;max-width:580px;background:#111623;border:1px solid var(--dt-line);border-radius:16px;overflow:hidden;color:#e5eaf4}
.dtabs .top{display:flex;align-items:center;justify-content:space-between;padding:16px 20px 0}
.dtabs .top h2{margin:0;font-size:.98rem;font-weight:700;line-height:normal}
.dtabs .nav-underline{--bs-nav-underline-gap:18px;--bs-nav-underline-border-width:2px;--bs-nav-underline-link-active-color:#fff;
  --bs-nav-link-padding-y:10px;--bs-nav-link-font-size:.86rem;--bs-nav-link-font-weight:600;--bs-nav-link-color:var(--dt-muted);--bs-nav-link-hover-color:#c6cede;
  flex-wrap:nowrap;padding:10px 20px 0;border-bottom:1px solid var(--dt-line)}
.dtabs .nav-underline .nav-link{display:flex;gap:7px;align-items:center;margin-bottom:-1px;padding:10px 2px 12px;line-height:normal;border-radius:4px 4px 0 0;
  transition:color .15s,border-color .15s}
.dtabs .nav-underline .nav-link:hover,.dtabs .nav-underline .nav-link:focus{border-bottom-color:transparent}
.dtabs .nav-underline .nav-link.active{font-weight:600;border-bottom-color:var(--dt-accent)}
.dtabs .badge{--bs-badge-padding-x:8px;--bs-badge-padding-y:2px;--bs-badge-font-size:.68rem;--bs-badge-font-weight:800;--bs-badge-color:#8ea0c0;
  background:var(--dt-pill);line-height:normal}
.dtabs .nav-link.active .badge{--bs-badge-color:var(--dt-accent);background:#0c3a44}
.dtabs .tab-pane{padding:20px;font-size:.86rem;color:#96a2bd;line-height:1.7}
.dtabs .row-item{display:flex;justify-content:space-between;gap:12px;padding:11px 12px;border:1px solid var(--dt-pill);border-radius:10px;margin-bottom:8px;color:#c6cede}
.dtabs .row-item span{color:#8190b0;font-size:.78rem;text-align:right}
.dtabs .nav-link:focus-visible{box-shadow:none;outline:2px solid var(--dt-accent);outline-offset:2px}
.dtabs .tab-pane:focus-visible{outline:2px solid var(--dt-accent);outline-offset:-6px;border-radius:0 0 16px 16px}
@media (prefers-reduced-motion:reduce){.dtabs .nav-link{transition:none}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div class="dtabs w-full max-w-[580px] overflow-hidden rounded-2xl border border-dt-line bg-[#111623] text-[#e5eaf4]">
  <div class="flex items-center justify-between px-5 pt-4"><h2 class="text-[.98rem] font-bold">Deployments</h2></div>
  <div role="tablist" aria-label="Deployment status" class="flex gap-[18px] border-b border-dt-line px-5 pt-2.5">
    <button type="button" role="tab" id="dt-tab-0" aria-selected="true" aria-controls="dt-panel-0" class="group -mb-px flex cursor-pointer items-center gap-[7px] rounded-t border-b-2 border-transparent px-0.5 pt-2.5 pb-3 text-[.86rem] font-semibold text-dt-muted transition-colors duration-150 hover:text-[#c6cede] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-dt-accent aria-selected:border-dt-accent aria-selected:text-white motion-reduce:transition-none">Active <span class="rounded-full bg-dt-pill px-2 py-0.5 text-[.68rem] font-extrabold text-[#8ea0c0] group-aria-selected:bg-[#0c3a44] group-aria-selected:text-dt-accent">3</span></button>
    <button type="button" role="tab" id="dt-tab-1" aria-selected="false" aria-controls="dt-panel-1" tabindex="-1" class="group -mb-px flex cursor-pointer items-center gap-[7px] rounded-t border-b-2 border-transparent px-0.5 pt-2.5 pb-3 text-[.86rem] font-semibold text-dt-muted transition-colors duration-150 hover:text-[#c6cede] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-dt-accent aria-selected:border-dt-accent aria-selected:text-white motion-reduce:transition-none">Queued <span class="rounded-full bg-dt-pill px-2 py-0.5 text-[.68rem] font-extrabold text-[#8ea0c0] group-aria-selected:bg-[#0c3a44] group-aria-selected:text-dt-accent">1</span></button>
    <button type="button" role="tab" id="dt-tab-2" aria-selected="false" aria-controls="dt-panel-2" tabindex="-1" class="group -mb-px flex cursor-pointer items-center gap-[7px] rounded-t border-b-2 border-transparent px-0.5 pt-2.5 pb-3 text-[.86rem] font-semibold text-dt-muted transition-colors duration-150 hover:text-[#c6cede] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-dt-accent aria-selected:border-dt-accent aria-selected:text-white motion-reduce:transition-none">Failed <span class="rounded-full bg-dt-pill px-2 py-0.5 text-[.68rem] font-extrabold text-[#8ea0c0] group-aria-selected:bg-[#0c3a44] group-aria-selected:text-dt-accent">2</span></button>
  </div>
  <div role="tabpanel" id="dt-panel-0" aria-labelledby="dt-tab-0" tabindex="0" class="p-5 text-[.86rem] leading-[1.7] text-[#96a2bd] focus-visible:rounded-b-2xl focus-visible:outline-2 focus-visible:-outline-offset-6 focus-visible:outline-dt-accent">
    <div class="mb-2 flex justify-between gap-3 rounded-[10px] border border-dt-pill px-3 py-[11px] text-[#c6cede]">api-gateway · v2.14.1 <span class="text-right text-[.78rem] text-[#8190b0]">2m ago</span></div>
    <div class="mb-2 flex justify-between gap-3 rounded-[10px] border border-dt-pill px-3 py-[11px] text-[#c6cede]">web-frontend · v8.0.3 <span class="text-right text-[.78rem] text-[#8190b0]">18m ago</span></div>
    <div class="mb-2 flex justify-between gap-3 rounded-[10px] border border-dt-pill px-3 py-[11px] text-[#c6cede]">billing-worker · v1.9.0 <span class="text-right text-[.78rem] text-[#8190b0]">1h ago</span></div>
  </div>
  <div role="tabpanel" id="dt-panel-1" aria-labelledby="dt-tab-1" tabindex="0" hidden class="p-5 text-[.86rem] leading-[1.7] text-[#96a2bd] focus-visible:rounded-b-2xl focus-visible:outline-2 focus-visible:-outline-offset-6 focus-visible:outline-dt-accent"><div class="mb-2 flex justify-between gap-3 rounded-[10px] border border-dt-pill px-3 py-[11px] text-[#c6cede]">search-index · v3.2.0 <span class="text-right text-[.78rem] text-[#8190b0]">waiting for approval</span></div></div>
  <div role="tabpanel" id="dt-panel-2" aria-labelledby="dt-tab-2" tabindex="0" hidden class="p-5 text-[.86rem] leading-[1.7] text-[#96a2bd] focus-visible:rounded-b-2xl focus-visible:outline-2 focus-visible:-outline-offset-6 focus-visible:outline-dt-accent">
    <div class="mb-2 flex justify-between gap-3 rounded-[10px] border border-dt-pill px-3 py-[11px] text-[#c6cede]">media-transcode · v0.9.1 <span class="text-right text-[.78rem] text-[#8190b0]">OOM at build</span></div>
    <div class="mb-2 flex justify-between gap-3 rounded-[10px] border border-dt-pill px-3 py-[11px] text-[#c6cede]">cron-runner · v2.0.0 <span class="text-right text-[.78rem] text-[#8190b0]">tests failed</span></div>
  </div>
</div>
CSS
@theme {
  --color-dt-accent: #22d3ee;
  --color-dt-muted: #77839e;
  --color-dt-line: #1f2739;
  --color-dt-pill: #1c2537;
}
JavaScript
/* WAI-ARIA tabs: click or use the arrow keys to select, Home/End jump to the first/last tab.
   Only the selected tab is in the Tab order (roving tabindex). */
document.querySelectorAll('.dtabs').forEach(root => {
  const list = root.querySelector('[role=tablist]');
  const tabs = [...list.querySelectorAll('[role=tab]')];
  const select = (tab, focus) => {
    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;
    });
    if (focus) tab.focus();
  };
  list.addEventListener('click', e => { const t = e.target.closest('[role=tab]'); if (t) select(t); });
  list.addEventListener('keydown', e => {
    const i = tabs.indexOf(e.target), n = tabs.length;
    const j = { ArrowRight: i + 1, ArrowDown: i + 1, ArrowLeft: i - 1 + n, ArrowUp: i - 1 + n, Home: 0, End: n - 1 }[e.key];
    if (i < 0 || j === undefined) return;
    e.preventDefault();
    select(tabs[j % n], true);
  });
});

About this set

A dark card titled Deployments with three underline tabs, Active, Queued and Failed, each followed by a small count pill, and a list of deployment rows under the selected tab. The selected tab turns white with a cyan underline, and its count pill switches from slate to cyan on a deep teal background, so the numbers stay readable in both states. Use it for dashboards, admin consoles, CI and deploy tools, and anywhere a dark interface needs to split one list by status. Each row shows a service name and version on the left and a timestamp or failure reason on the right, and the rows wrap cleanly on narrow screens. The script handles aria-selected, the roving tabindex and hidden panels. The Bootstrap version is .nav-underline with the Tab plugin, recoloured through the –bs-nav-underline-* variables, with .badge pills for the counts; the Tailwind version uses aria-selected utilities, and group-aria-selected utilities for the pills.

What’s included

  • Underline tabs with count pills that change with the selection
  • Built for dark dashboards and consoles
  • Bootstrap version uses .nav-underline and .badge
  • Row timestamps lightened to meet 4.5:1 on the dark card
  • Heading marked up as a real h2

Accessibility

The tabs are buttons in a tablist labelled Deployment status, so each is announced with its count, for example Active 3, and controls a panel labelled by the tab. Only the selected tab is in the Tab order; Left and Right (or Up and Down) move between tabs and select them, and Home and End jump to the first and last tab. Focus draws a 2px cyan outline around the tab or inside the panel, and colour transitions are removed under prefers-reduced-motion.

Customise it

Change --dt-accent, --dt-muted, --dt-line and --dt-pill on .dtabs in plain CSS and Bootstrap, where the tab gap and underline width come from --bs-nav-underline-gap and --bs-nav-underline-border-width. In Tailwind edit the --color-dt-* theme colours.