Open demo

CSS code

HTML
<div class="ntabs">
  <div role="tablist" aria-label="Section" class="l1">
    <button type="button" role="tab" id="nt-tab-1" aria-selected="true" aria-controls="nt-panel-1">Account</button>
    <button type="button" role="tab" id="nt-tab-2" aria-selected="false" aria-controls="nt-panel-2" tabindex="-1">Billing</button>
    <button type="button" role="tab" id="nt-tab-3" aria-selected="false" aria-controls="nt-panel-3" tabindex="-1">Security</button>
  </div>
  <div role="tabpanel" id="nt-panel-1" aria-labelledby="nt-tab-1">
    <div role="tablist" aria-label="Account view" class="l2">
      <button type="button" role="tab" id="nt-tab-1-1" aria-selected="true" aria-controls="nt-panel-1-1">Profile</button>
      <button type="button" role="tab" id="nt-tab-1-2" aria-selected="false" aria-controls="nt-panel-1-2" tabindex="-1">Preferences</button>
      <button type="button" role="tab" id="nt-tab-1-3" aria-selected="false" aria-controls="nt-panel-1-3" tabindex="-1">Sessions</button>
    </div>
    <div role="tabpanel" id="nt-panel-1-1" aria-labelledby="nt-tab-1-1" tabindex="0"><p>Your name, avatar and the address other people see. Each section keeps its own second-level choice, because every row is a separate tablist.</p></div>
    <div role="tabpanel" id="nt-panel-1-2" aria-labelledby="nt-tab-1-2" tabindex="0" hidden><p>Language, timezone and the theme this account defaults to.</p></div>
    <div role="tabpanel" id="nt-panel-1-3" aria-labelledby="nt-tab-1-3" tabindex="0" hidden><p>Every device currently signed in, with a way to end any of them.</p></div>
  </div>
  <div role="tabpanel" id="nt-panel-2" aria-labelledby="nt-tab-2" hidden>
    <div role="tablist" aria-label="Billing view" class="l2">
      <button type="button" role="tab" id="nt-tab-2-1" aria-selected="true" aria-controls="nt-panel-2-1">Plan</button>
      <button type="button" role="tab" id="nt-tab-2-2" aria-selected="false" aria-controls="nt-panel-2-2" tabindex="-1">Invoices</button>
      <button type="button" role="tab" id="nt-tab-2-3" aria-selected="false" aria-controls="nt-panel-2-3" tabindex="-1">Payment</button>
    </div>
    <div role="tabpanel" id="nt-panel-2-1" aria-labelledby="nt-tab-2-1" tabindex="0"><p>You are on Team, billed yearly. 12 of 25 seats are in use.</p></div>
    <div role="tabpanel" id="nt-panel-2-2" aria-labelledby="nt-tab-2-2" tabindex="0" hidden><p>Every invoice since the account opened, downloadable as PDF.</p></div>
    <div role="tabpanel" id="nt-panel-2-3" aria-labelledby="nt-tab-2-3" tabindex="0" hidden><p>The card on file and the billing address attached to it.</p></div>
  </div>
  <div role="tabpanel" id="nt-panel-3" aria-labelledby="nt-tab-3" hidden>
    <div role="tablist" aria-label="Security view" class="l2">
      <button type="button" role="tab" id="nt-tab-3-1" aria-selected="true" aria-controls="nt-panel-3-1">Password</button>
      <button type="button" role="tab" id="nt-tab-3-2" aria-selected="false" aria-controls="nt-panel-3-2" tabindex="-1">Two-factor</button>
      <button type="button" role="tab" id="nt-tab-3-3" aria-selected="false" aria-controls="nt-panel-3-3" tabindex="-1">Audit log</button>
    </div>
    <div role="tabpanel" id="nt-panel-3-1" aria-labelledby="nt-tab-3-1" tabindex="0"><p>Last changed 94 days ago. A password manager is the shortest route to a good one.</p></div>
    <div role="tabpanel" id="nt-panel-3-2" aria-labelledby="nt-tab-3-2" tabindex="0" hidden><p>Two-factor is on, using an authenticator app. Recovery codes were generated in March.</p></div>
    <div role="tabpanel" id="nt-panel-3-3" aria-labelledby="nt-tab-3-3" tabindex="0" hidden><p>Every sign-in, permission change and export, kept for 90 days.</p></div>
  </div>
</div>
CSS
/* Two-level tabs: an underlined section row, and inside each section panel a segmented row of views. Each row is its own tablist. */
.ntabs{--nt-accent:#4f46e5;--nt-muted1:#5f6b85;--nt-muted2:#5d6a86;--nt-text:#16203a;
  box-sizing:border-box;width:100%;max-width:580px;padding:24px 26px 26px;background:#fff;border:1px solid #e3e8f2;border-radius:16px;
  box-shadow:0 16px 38px -24px rgba(16,24,48,.24);color:var(--nt-text)}
.ntabs [role=tab]{margin:0;border:0;background:none;font:inherit;cursor:pointer}
.ntabs .l1{display:flex;gap:22px;border-bottom:1px solid #e6eaf3;margin-bottom:16px}
.ntabs .l1 [role=tab]{padding:0 0 11px;margin-bottom:-1px;border-bottom:2px solid transparent;border-radius:4px 4px 0 0;font-size:.92rem;font-weight:700;
  color:var(--nt-muted1);transition:color .15s,border-color .15s}
.ntabs .l1 [role=tab]:hover{color:var(--nt-text)}
.ntabs .l1 [role=tab][aria-selected=true]{color:var(--nt-accent);border-color:var(--nt-accent)}
.ntabs .l2{display:flex;gap:6px;width:max-content;max-width:100%;overflow-x:auto;scrollbar-width:none;background:#f2f5fa;border-radius:10px;padding:4px;margin-bottom:18px}
.ntabs .l2 [role=tab]{flex:0 0 auto;padding:7px 14px;border-radius:7px;font-size:.82rem;font-weight:700;color:var(--nt-muted2);white-space:nowrap}
.ntabs .l2 [role=tab]:hover{color:var(--nt-text)}
.ntabs .l2 [role=tab][aria-selected=true]{background:#fff;color:var(--nt-text);box-shadow:0 1px 3px rgba(16,24,48,.14)}
.ntabs p{margin:0;font-size:.9rem;line-height:1.7;color:#4a5670}
.ntabs [role=tabpanel] [role=tabpanel]{border-radius:6px}
.ntabs .l1 [role=tab]:focus-visible{outline:2px solid var(--nt-accent);outline-offset:2px}
.ntabs .l2 [role=tab]:focus-visible{outline:2px solid var(--nt-accent);outline-offset:-2px}
.ntabs [role=tabpanel]:focus-visible{outline:2px solid var(--nt-accent);outline-offset:4px}
.ntabs [hidden]{display:none}
@media (prefers-reduced-motion:reduce){.ntabs .l1 [role=tab]{transition:none}}
JavaScript
/* Two independent WAI-ARIA tablists: the section row, and a view row inside each section panel.
   In each row, click or use the arrow keys to select and Home/End to jump; only the selected tab is in the Tab order. */
document.querySelectorAll('.ntabs [role=tablist]').forEach(list => {
  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="ntabs">
  <ul class="nav nav-underline l1" role="tablist" aria-label="Section">
    <li class="nav-item" role="presentation"><button class="nav-link active" type="button" id="nt-tab-1" data-bs-toggle="tab" data-bs-target="#nt-panel-1" role="tab" aria-controls="nt-panel-1" aria-selected="true">Account</button></li>
    <li class="nav-item" role="presentation"><button class="nav-link" type="button" id="nt-tab-2" data-bs-toggle="tab" data-bs-target="#nt-panel-2" role="tab" aria-controls="nt-panel-2" aria-selected="false" tabindex="-1">Billing</button></li>
    <li class="nav-item" role="presentation"><button class="nav-link" type="button" id="nt-tab-3" data-bs-toggle="tab" data-bs-target="#nt-panel-3" role="tab" aria-controls="nt-panel-3" aria-selected="false" tabindex="-1">Security</button></li>
  </ul>
  <div class="tab-content">
    <div class="tab-pane active" id="nt-panel-1" role="tabpanel" aria-labelledby="nt-tab-1">
      <ul class="nav nav-pills l2" role="tablist" aria-label="Account view">
        <li class="nav-item" role="presentation"><button class="nav-link active" type="button" id="nt-tab-1-1" data-bs-toggle="pill" data-bs-target="#nt-panel-1-1" role="tab" aria-controls="nt-panel-1-1" aria-selected="true">Profile</button></li>
        <li class="nav-item" role="presentation"><button class="nav-link" type="button" id="nt-tab-1-2" data-bs-toggle="pill" data-bs-target="#nt-panel-1-2" role="tab" aria-controls="nt-panel-1-2" aria-selected="false" tabindex="-1">Preferences</button></li>
        <li class="nav-item" role="presentation"><button class="nav-link" type="button" id="nt-tab-1-3" data-bs-toggle="pill" data-bs-target="#nt-panel-1-3" role="tab" aria-controls="nt-panel-1-3" aria-selected="false" tabindex="-1">Sessions</button></li>
      </ul>
      <div class="tab-content">
        <div class="tab-pane active" id="nt-panel-1-1" role="tabpanel" aria-labelledby="nt-tab-1-1" tabindex="0"><p>Your name, avatar and the address other people see. Each section keeps its own second-level choice, because every row is a separate tablist.</p></div>
        <div class="tab-pane" id="nt-panel-1-2" role="tabpanel" aria-labelledby="nt-tab-1-2" tabindex="0"><p>Language, timezone and the theme this account defaults to.</p></div>
        <div class="tab-pane" id="nt-panel-1-3" role="tabpanel" aria-labelledby="nt-tab-1-3" tabindex="0"><p>Every device currently signed in, with a way to end any of them.</p></div>
      </div>
    </div>
    <div class="tab-pane" id="nt-panel-2" role="tabpanel" aria-labelledby="nt-tab-2">
      <ul class="nav nav-pills l2" role="tablist" aria-label="Billing view">
        <li class="nav-item" role="presentation"><button class="nav-link active" type="button" id="nt-tab-2-1" data-bs-toggle="pill" data-bs-target="#nt-panel-2-1" role="tab" aria-controls="nt-panel-2-1" aria-selected="true">Plan</button></li>
        <li class="nav-item" role="presentation"><button class="nav-link" type="button" id="nt-tab-2-2" data-bs-toggle="pill" data-bs-target="#nt-panel-2-2" role="tab" aria-controls="nt-panel-2-2" aria-selected="false" tabindex="-1">Invoices</button></li>
        <li class="nav-item" role="presentation"><button class="nav-link" type="button" id="nt-tab-2-3" data-bs-toggle="pill" data-bs-target="#nt-panel-2-3" role="tab" aria-controls="nt-panel-2-3" aria-selected="false" tabindex="-1">Payment</button></li>
      </ul>
      <div class="tab-content">
        <div class="tab-pane active" id="nt-panel-2-1" role="tabpanel" aria-labelledby="nt-tab-2-1" tabindex="0"><p>You are on Team, billed yearly. 12 of 25 seats are in use.</p></div>
        <div class="tab-pane" id="nt-panel-2-2" role="tabpanel" aria-labelledby="nt-tab-2-2" tabindex="0"><p>Every invoice since the account opened, downloadable as PDF.</p></div>
        <div class="tab-pane" id="nt-panel-2-3" role="tabpanel" aria-labelledby="nt-tab-2-3" tabindex="0"><p>The card on file and the billing address attached to it.</p></div>
      </div>
    </div>
    <div class="tab-pane" id="nt-panel-3" role="tabpanel" aria-labelledby="nt-tab-3">
      <ul class="nav nav-pills l2" role="tablist" aria-label="Security view">
        <li class="nav-item" role="presentation"><button class="nav-link active" type="button" id="nt-tab-3-1" data-bs-toggle="pill" data-bs-target="#nt-panel-3-1" role="tab" aria-controls="nt-panel-3-1" aria-selected="true">Password</button></li>
        <li class="nav-item" role="presentation"><button class="nav-link" type="button" id="nt-tab-3-2" data-bs-toggle="pill" data-bs-target="#nt-panel-3-2" role="tab" aria-controls="nt-panel-3-2" aria-selected="false" tabindex="-1">Two-factor</button></li>
        <li class="nav-item" role="presentation"><button class="nav-link" type="button" id="nt-tab-3-3" data-bs-toggle="pill" data-bs-target="#nt-panel-3-3" role="tab" aria-controls="nt-panel-3-3" aria-selected="false" tabindex="-1">Audit log</button></li>
      </ul>
      <div class="tab-content">
        <div class="tab-pane active" id="nt-panel-3-1" role="tabpanel" aria-labelledby="nt-tab-3-1" tabindex="0"><p>Last changed 94 days ago. A password manager is the shortest route to a good one.</p></div>
        <div class="tab-pane" id="nt-panel-3-2" role="tabpanel" aria-labelledby="nt-tab-3-2" tabindex="0"><p>Two-factor is on, using an authenticator app. Recovery codes were generated in March.</p></div>
        <div class="tab-pane" id="nt-panel-3-3" role="tabpanel" aria-labelledby="nt-tab-3-3" tabindex="0"><p>Every sign-in, permission change and export, kept for 90 days.</p></div>
      </div>
    </div>
  </div>
</div>
CSS
/* Bootstrap .nav-underline for sections and .nav-pills for views inside each section, both run by the Tab plugin
   and restyled through the --bs-nav-* variables. Each row is its own tablist. */
.ntabs{--nt-accent:#4f46e5;--nt-muted1:#5f6b85;--nt-muted2:#5d6a86;--nt-text:#16203a;
  width:100%;max-width:580px;padding:24px 26px 26px;background:#fff;border:1px solid #e3e8f2;border-radius:16px;
  box-shadow:0 16px 38px -24px rgba(16,24,48,.24);color:var(--nt-text)}
.ntabs .l1{--bs-nav-underline-gap:22px;--bs-nav-underline-border-width:2px;--bs-nav-underline-link-active-color:var(--nt-accent);
  --bs-nav-link-padding-y:0;--bs-nav-link-font-size:.92rem;--bs-nav-link-font-weight:700;--bs-nav-link-color:var(--nt-muted1);--bs-nav-link-hover-color:var(--nt-text);
  flex-wrap:nowrap;border-bottom:1px solid #e6eaf3;margin-bottom:16px}
.ntabs .l1 .nav-link{padding-bottom:11px;margin-bottom:-1px;line-height:normal;border-radius:4px 4px 0 0;transition:color .15s,border-color .15s}
.ntabs .l1 .nav-link:hover,.ntabs .l1 .nav-link:focus{border-bottom-color:transparent}
.ntabs .l1 .nav-link.active{font-weight:700;border-bottom-color:var(--nt-accent)}
.ntabs .l2{--bs-nav-link-padding-x:14px;--bs-nav-link-padding-y:7px;--bs-nav-link-font-size:.82rem;--bs-nav-link-font-weight:700;
  --bs-nav-link-color:var(--nt-muted2);--bs-nav-link-hover-color:var(--nt-text);
  --bs-nav-pills-border-radius:7px;--bs-nav-pills-link-active-bg:#fff;--bs-nav-pills-link-active-color:var(--nt-text);
  flex-wrap:nowrap;gap:6px;width:max-content;max-width:100%;overflow-x:auto;scrollbar-width:none;background:#f2f5fa;border-radius:10px;padding:4px;margin-bottom:18px}
.ntabs .l2 .nav-item{flex:0 0 auto}
.ntabs .l2 .nav-link{line-height:normal;white-space:nowrap;transition:none}
.ntabs .l2 .nav-link.active{box-shadow:0 1px 3px rgba(16,24,48,.14)}
.ntabs p{margin:0;font-size:.9rem;line-height:1.7;color:#4a5670}
.ntabs .tab-pane .tab-pane{border-radius:6px}
.ntabs .l1 .nav-link:focus-visible{box-shadow:none;outline:2px solid var(--nt-accent);outline-offset:2px}
.ntabs .l2 .nav-link:focus-visible{outline:2px solid var(--nt-accent);outline-offset:-2px}
.ntabs .tab-pane:focus-visible{outline:2px solid var(--nt-accent);outline-offset:4px}
@media (prefers-reduced-motion:reduce){.ntabs .l1 .nav-link{transition:none}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div class="ntabs w-full max-w-[580px] rounded-2xl border border-[#e3e8f2] bg-white px-[26px] pt-6 pb-[26px] text-nt-text shadow-[0_16px_38px_-24px_rgba(16,24,48,.24)]">
  <div role="tablist" aria-label="Section" class="mb-4 flex gap-[22px] border-b border-[#e6eaf3]">
    <button type="button" role="tab" id="nt-tab-1" aria-selected="true" aria-controls="nt-panel-1" class="-mb-px cursor-pointer rounded-t border-b-2 border-transparent pb-[11px] text-[.92rem] font-bold text-nt-muted1 transition-colors duration-150 hover:text-nt-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nt-accent aria-selected:border-nt-accent aria-selected:text-nt-accent motion-reduce:transition-none">Account</button>
    <button type="button" role="tab" id="nt-tab-2" aria-selected="false" aria-controls="nt-panel-2" tabindex="-1" class="-mb-px cursor-pointer rounded-t border-b-2 border-transparent pb-[11px] text-[.92rem] font-bold text-nt-muted1 transition-colors duration-150 hover:text-nt-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nt-accent aria-selected:border-nt-accent aria-selected:text-nt-accent motion-reduce:transition-none">Billing</button>
    <button type="button" role="tab" id="nt-tab-3" aria-selected="false" aria-controls="nt-panel-3" tabindex="-1" class="-mb-px cursor-pointer rounded-t border-b-2 border-transparent pb-[11px] text-[.92rem] font-bold text-nt-muted1 transition-colors duration-150 hover:text-nt-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nt-accent aria-selected:border-nt-accent aria-selected:text-nt-accent motion-reduce:transition-none">Security</button>
  </div>
  <div role="tabpanel" id="nt-panel-1" aria-labelledby="nt-tab-1">
    <div role="tablist" aria-label="Account view" class="mb-[18px] flex w-max max-w-full gap-1.5 overflow-x-auto rounded-[10px] bg-[#f2f5fa] p-1 [scrollbar-width:none]">
      <button type="button" role="tab" id="nt-tab-1-1" aria-selected="true" aria-controls="nt-panel-1-1" class="shrink-0 cursor-pointer rounded-[7px] px-3.5 py-[7px] text-[.82rem] font-bold whitespace-nowrap text-nt-muted2 hover:text-nt-text focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nt-accent aria-selected:bg-white aria-selected:text-nt-text aria-selected:shadow-[0_1px_3px_rgba(16,24,48,.14)]">Profile</button>
      <button type="button" role="tab" id="nt-tab-1-2" aria-selected="false" aria-controls="nt-panel-1-2" tabindex="-1" class="shrink-0 cursor-pointer rounded-[7px] px-3.5 py-[7px] text-[.82rem] font-bold whitespace-nowrap text-nt-muted2 hover:text-nt-text focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nt-accent aria-selected:bg-white aria-selected:text-nt-text aria-selected:shadow-[0_1px_3px_rgba(16,24,48,.14)]">Preferences</button>
      <button type="button" role="tab" id="nt-tab-1-3" aria-selected="false" aria-controls="nt-panel-1-3" tabindex="-1" class="shrink-0 cursor-pointer rounded-[7px] px-3.5 py-[7px] text-[.82rem] font-bold whitespace-nowrap text-nt-muted2 hover:text-nt-text focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nt-accent aria-selected:bg-white aria-selected:text-nt-text aria-selected:shadow-[0_1px_3px_rgba(16,24,48,.14)]">Sessions</button>
    </div>
    <div role="tabpanel" id="nt-panel-1-1" aria-labelledby="nt-tab-1-1" tabindex="0" class="rounded-md focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-nt-accent"><p class="text-[.9rem] leading-[1.7] text-[#4a5670]">Your name, avatar and the address other people see. Each section keeps its own second-level choice, because every row is a separate tablist.</p></div>
    <div role="tabpanel" id="nt-panel-1-2" aria-labelledby="nt-tab-1-2" tabindex="0" hidden class="rounded-md focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-nt-accent"><p class="text-[.9rem] leading-[1.7] text-[#4a5670]">Language, timezone and the theme this account defaults to.</p></div>
    <div role="tabpanel" id="nt-panel-1-3" aria-labelledby="nt-tab-1-3" tabindex="0" hidden class="rounded-md focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-nt-accent"><p class="text-[.9rem] leading-[1.7] text-[#4a5670]">Every device currently signed in, with a way to end any of them.</p></div>
  </div>
  <div role="tabpanel" id="nt-panel-2" aria-labelledby="nt-tab-2" hidden>
    <div role="tablist" aria-label="Billing view" class="mb-[18px] flex w-max max-w-full gap-1.5 overflow-x-auto rounded-[10px] bg-[#f2f5fa] p-1 [scrollbar-width:none]">
      <button type="button" role="tab" id="nt-tab-2-1" aria-selected="true" aria-controls="nt-panel-2-1" class="shrink-0 cursor-pointer rounded-[7px] px-3.5 py-[7px] text-[.82rem] font-bold whitespace-nowrap text-nt-muted2 hover:text-nt-text focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nt-accent aria-selected:bg-white aria-selected:text-nt-text aria-selected:shadow-[0_1px_3px_rgba(16,24,48,.14)]">Plan</button>
      <button type="button" role="tab" id="nt-tab-2-2" aria-selected="false" aria-controls="nt-panel-2-2" tabindex="-1" class="shrink-0 cursor-pointer rounded-[7px] px-3.5 py-[7px] text-[.82rem] font-bold whitespace-nowrap text-nt-muted2 hover:text-nt-text focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nt-accent aria-selected:bg-white aria-selected:text-nt-text aria-selected:shadow-[0_1px_3px_rgba(16,24,48,.14)]">Invoices</button>
      <button type="button" role="tab" id="nt-tab-2-3" aria-selected="false" aria-controls="nt-panel-2-3" tabindex="-1" class="shrink-0 cursor-pointer rounded-[7px] px-3.5 py-[7px] text-[.82rem] font-bold whitespace-nowrap text-nt-muted2 hover:text-nt-text focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nt-accent aria-selected:bg-white aria-selected:text-nt-text aria-selected:shadow-[0_1px_3px_rgba(16,24,48,.14)]">Payment</button>
    </div>
    <div role="tabpanel" id="nt-panel-2-1" aria-labelledby="nt-tab-2-1" tabindex="0" class="rounded-md focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-nt-accent"><p class="text-[.9rem] leading-[1.7] text-[#4a5670]">You are on Team, billed yearly. 12 of 25 seats are in use.</p></div>
    <div role="tabpanel" id="nt-panel-2-2" aria-labelledby="nt-tab-2-2" tabindex="0" hidden class="rounded-md focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-nt-accent"><p class="text-[.9rem] leading-[1.7] text-[#4a5670]">Every invoice since the account opened, downloadable as PDF.</p></div>
    <div role="tabpanel" id="nt-panel-2-3" aria-labelledby="nt-tab-2-3" tabindex="0" hidden class="rounded-md focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-nt-accent"><p class="text-[.9rem] leading-[1.7] text-[#4a5670]">The card on file and the billing address attached to it.</p></div>
  </div>
  <div role="tabpanel" id="nt-panel-3" aria-labelledby="nt-tab-3" hidden>
    <div role="tablist" aria-label="Security view" class="mb-[18px] flex w-max max-w-full gap-1.5 overflow-x-auto rounded-[10px] bg-[#f2f5fa] p-1 [scrollbar-width:none]">
      <button type="button" role="tab" id="nt-tab-3-1" aria-selected="true" aria-controls="nt-panel-3-1" class="shrink-0 cursor-pointer rounded-[7px] px-3.5 py-[7px] text-[.82rem] font-bold whitespace-nowrap text-nt-muted2 hover:text-nt-text focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nt-accent aria-selected:bg-white aria-selected:text-nt-text aria-selected:shadow-[0_1px_3px_rgba(16,24,48,.14)]">Password</button>
      <button type="button" role="tab" id="nt-tab-3-2" aria-selected="false" aria-controls="nt-panel-3-2" tabindex="-1" class="shrink-0 cursor-pointer rounded-[7px] px-3.5 py-[7px] text-[.82rem] font-bold whitespace-nowrap text-nt-muted2 hover:text-nt-text focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nt-accent aria-selected:bg-white aria-selected:text-nt-text aria-selected:shadow-[0_1px_3px_rgba(16,24,48,.14)]">Two-factor</button>
      <button type="button" role="tab" id="nt-tab-3-3" aria-selected="false" aria-controls="nt-panel-3-3" tabindex="-1" class="shrink-0 cursor-pointer rounded-[7px] px-3.5 py-[7px] text-[.82rem] font-bold whitespace-nowrap text-nt-muted2 hover:text-nt-text focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nt-accent aria-selected:bg-white aria-selected:text-nt-text aria-selected:shadow-[0_1px_3px_rgba(16,24,48,.14)]">Audit log</button>
    </div>
    <div role="tabpanel" id="nt-panel-3-1" aria-labelledby="nt-tab-3-1" tabindex="0" class="rounded-md focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-nt-accent"><p class="text-[.9rem] leading-[1.7] text-[#4a5670]">Last changed 94 days ago. A password manager is the shortest route to a good one.</p></div>
    <div role="tabpanel" id="nt-panel-3-2" aria-labelledby="nt-tab-3-2" tabindex="0" hidden class="rounded-md focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-nt-accent"><p class="text-[.9rem] leading-[1.7] text-[#4a5670]">Two-factor is on, using an authenticator app. Recovery codes were generated in March.</p></div>
    <div role="tabpanel" id="nt-panel-3-3" aria-labelledby="nt-tab-3-3" tabindex="0" hidden class="rounded-md focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-nt-accent"><p class="text-[.9rem] leading-[1.7] text-[#4a5670]">Every sign-in, permission change and export, kept for 90 days.</p></div>
  </div>
</div>
CSS
@theme {
  --color-nt-accent: #4f46e5;
  --color-nt-muted1: #5f6b85;
  --color-nt-muted2: #5d6a86;
  --color-nt-text: #16203a;
}
JavaScript
/* Two independent WAI-ARIA tablists: the section row, and a view row inside each section panel.
   In each row, click or use the arrow keys to select and Home/End to jump; only the selected tab is in the Tab order. */
document.querySelectorAll('.ntabs [role=tablist]').forEach(list => {
  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

Two rows of tabs in one card: an underlined section row, Account, Billing and Security, and inside each section a grey segmented row of views, such as Profile, Preferences and Sessions under Account or Password, Two-factor and Audit log under Security. The selected section turns indigo with an underline, and the selected view becomes a white chip with a light shadow. Use it for account pages, admin settings and reports where a handful of sections each have a few sub-views, without building a full sidebar. Every row is its own tablist, so each section remembers which view was open when you come back to it. The inner row keeps its width to its content and scrolls sideways if a narrow screen cannot fit it. One short script wires up every tablist in the component with the same selection, arrow key and roving tabindex behaviour. The Bootstrap version uses .nav-underline for sections and .nav-pills for views, both run by the Tab plugin, and the Tailwind version styles both rows with aria-selected utilities.

What’s included

  • Section row plus a view row inside each section
  • Each section remembers its own view
  • One script drives any number of tablists
  • Inner row scrolls instead of overflowing on phones
  • Converted from shared radio groups to real ARIA tabs

Accessibility

The section row is a tablist labelled Section, and each section panel contains its own tablist labelled, for example, Account view. In each row, 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 indigo outline, and the section row's colour transition is removed under prefers-reduced-motion.

Customise it

Change --nt-accent, --nt-muted1, --nt-muted2 and --nt-text on .ntabs in plain CSS and Bootstrap, where the section row also reads --bs-nav-underline-gap and the view row --bs-nav-pills-link-active-bg. In Tailwind edit the --color-nt-* theme colours.