Open demo

CSS code

HTML
<div class="w">
  <div class="hd"><h2>Nav overflow</h2><p>Narrow the window — the tabs that stop fitting move into More.</p></div>
  <div class="bar">
    <div class="tabs" role="tablist" aria-label="Workspace">
      <button type="button" role="tab" id="ws-tab-0" aria-controls="ws-panel-0" aria-selected="true">Overview</button>
      <button type="button" role="tab" id="ws-tab-1" aria-controls="ws-panel-1" aria-selected="false" tabindex="-1">Activity</button>
      <button type="button" role="tab" id="ws-tab-2" aria-controls="ws-panel-2" aria-selected="false" tabindex="-1">Members</button>
      <button type="button" role="tab" id="ws-tab-3" aria-controls="ws-panel-3" aria-selected="false" tabindex="-1">Integrations</button>
      <button type="button" role="tab" id="ws-tab-4" aria-controls="ws-panel-4" aria-selected="false" tabindex="-1">Permissions</button>
      <button type="button" role="tab" id="ws-tab-5" aria-controls="ws-panel-5" aria-selected="false" tabindex="-1">Billing</button>
    </div>
    <details class="more" hidden><summary>More <span aria-hidden="true">▾</span></summary><div class="pop"></div></details>
  </div>
  <div class="panel" id="ws-panel-0" role="tabpanel" aria-labelledby="ws-tab-0" tabindex="0">Everything at a glance — the tab that never gets folded away, because it is first.</div>
  <div class="panel" id="ws-panel-1" role="tabpanel" aria-labelledby="ws-tab-1" tabindex="0" hidden>Every action taken in this workspace, newest first.</div>
  <div class="panel" id="ws-panel-2" role="tabpanel" aria-labelledby="ws-tab-2" tabindex="0" hidden>Twelve people, three of them administrators.</div>
  <div class="panel" id="ws-panel-3" role="tabpanel" aria-labelledby="ws-tab-3" tabindex="0" hidden>Connected tools and the scopes each was granted.</div>
  <div class="panel" id="ws-panel-4" role="tabpanel" aria-labelledby="ws-tab-4" tabindex="0" hidden>Who can see what, and who is allowed to change it.</div>
  <div class="panel" id="ws-panel-5" role="tabpanel" aria-labelledby="ws-tab-5" tabindex="0" hidden>Plan, invoices and the card on file.</div>
</div>
CSS
/* Overflow tabs: a WAI-ARIA tablist that measures itself and folds the tabs that do not fit into a
   <details> More menu. The selected tab is never folded away. */
.w{width:100%;max-width:600px;padding:24px 26px 26px;background:#fff;border:1px solid #e3e8f2;border-radius:16px;
  box-shadow:0 16px 38px -24px rgba(16,24,48,.24)}
.hd h2{font-size:1rem;margin:0 0 3px}
.hd p{font-size:.82rem;color:#5d6a86;margin:0 0 16px}
.bar{display:flex;align-items:center;gap:4px;border-bottom:1px solid #e6eaf3;margin-bottom:18px}
.tabs{display:flex;gap:4px;flex:1;min-width:0}
.tabs button,.pop button{border:0;background:none;font:inherit;font-size:.87rem;font-weight:700;color:#5d6a86;
  padding:10px 13px;cursor:pointer;white-space:nowrap}
.tabs button{border-bottom:2px solid transparent;margin-bottom:-1px;border-radius:8px 8px 0 0}
.tabs button:hover,.pop button:hover{color:#16203a;background:#f6f8fc}
.tabs button[aria-selected=true]{color:#4f46e5;border-color:#4f46e5}
.tabs button[hidden],.more[hidden]{display:none}
.more{position:relative;flex-shrink:0}
.more summary{list-style:none;cursor:pointer;font-size:.87rem;font-weight:700;color:#5d6a86;
  padding:10px 12px;border-radius:8px;white-space:nowrap}
.more summary::-webkit-details-marker{display:none}
.more summary:hover{background:#f6f8fc;color:#16203a}
.pop{position:absolute;top:calc(100% + 6px);right:0;background:#fff;border:1px solid #e3e8f2;
  border-radius:11px;padding:6px;min-width:170px;z-index:5;box-shadow:0 16px 34px -18px rgba(16,24,48,.3)}
.pop button{display:block;width:100%;text-align:left;border-radius:8px}
.tabs button:focus-visible,.more summary:focus-visible,.pop button:focus-visible{outline:2px solid #4f46e5;outline-offset:-2px}
.panel{font-size:.89rem;line-height:1.7;color:#4a5670;border-radius:4px}
.panel:focus-visible{outline:2px solid #4f46e5;outline-offset:4px}
JavaScript
// Tabs that fold into a More menu. The fitting depends on measured widths, which CSS cannot do:
// tabs are hidden from the end until the row fits, and each hidden tab gets a button in the menu.
// Keyboard: arrow keys move between the visible tabs, Home/End jump to the ends, Escape closes the menu.
const list = document.querySelector('.bar [role=tablist]');
const more = document.querySelector('.bar .more');
const pop = more.querySelector('.pop');
const tabs = [...list.querySelectorAll('[role=tab]')];
const isSelected = t => t.getAttribute('aria-selected') === 'true';

const layout = () => {
  tabs.forEach(t => { t.hidden = false; });
  more.hidden = true;
  const folded = [];
  // Never fold the first tab or the selected one.
  for (let i = tabs.length - 1; i > 0 && list.scrollWidth > list.clientWidth; i--) {
    if (isSelected(tabs[i])) continue;
    tabs[i].hidden = true;
    folded.unshift(tabs[i]);
    more.hidden = false;
  }
  pop.replaceChildren(...folded.map(t => {
    const b = document.createElement('button');
    b.type = 'button';
    b.textContent = t.textContent;
    b.dataset.tab = t.id;
    return b;
  }));
  if (!folded.length) more.open = false;
};

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;
  });
  layout();
};

list.addEventListener('click', e => { const t = e.target.closest('[role=tab]'); if (t) select(t); });
list.addEventListener('keydown', e => {
  const shown = tabs.filter(t => !t.hidden), i = shown.indexOf(e.target);
  const n = { ArrowLeft: i - 1, ArrowRight: i + 1, Home: 0, End: shown.length - 1 }[e.key];
  if (n === undefined || i < 0) return;
  e.preventDefault();
  const t = shown[(n + shown.length) % shown.length];
  t.focus();
  select(t);
});
pop.addEventListener('click', e => {
  const b = e.target.closest('button');
  if (!b) return;
  const tab = document.getElementById(b.dataset.tab);
  more.open = false;
  select(tab);
  tab.focus();
});
more.addEventListener('keydown', e => {
  if (e.key === 'Escape' && more.open) { more.open = false; more.querySelector('summary').focus(); }
});
document.addEventListener('click', e => { if (!more.contains(e.target)) more.open = false; });

new ResizeObserver(layout).observe(list.parentElement);
layout();

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS, Bootstrap 5.3.8 JS bundle

HTML
<div class="w">
  <div class="hd"><h2>Nav overflow</h2><p>Narrow the window — the tabs that stop fitting move into More.</p></div>
  <div class="bar">
    <div class="nav nav-underline tabs" role="tablist" aria-label="Workspace">
      <button class="nav-link active" id="ws-tab-0" data-bs-toggle="tab" data-bs-target="#ws-panel-0" type="button" role="tab" aria-controls="ws-panel-0" aria-selected="true">Overview</button>
      <button class="nav-link" id="ws-tab-1" data-bs-toggle="tab" data-bs-target="#ws-panel-1" type="button" role="tab" aria-controls="ws-panel-1" aria-selected="false" tabindex="-1">Activity</button>
      <button class="nav-link" id="ws-tab-2" data-bs-toggle="tab" data-bs-target="#ws-panel-2" type="button" role="tab" aria-controls="ws-panel-2" aria-selected="false" tabindex="-1">Members</button>
      <button class="nav-link" id="ws-tab-3" data-bs-toggle="tab" data-bs-target="#ws-panel-3" type="button" role="tab" aria-controls="ws-panel-3" aria-selected="false" tabindex="-1">Integrations</button>
      <button class="nav-link" id="ws-tab-4" data-bs-toggle="tab" data-bs-target="#ws-panel-4" type="button" role="tab" aria-controls="ws-panel-4" aria-selected="false" tabindex="-1">Permissions</button>
      <button class="nav-link" id="ws-tab-5" data-bs-toggle="tab" data-bs-target="#ws-panel-5" type="button" role="tab" aria-controls="ws-panel-5" aria-selected="false" tabindex="-1">Billing</button>
    </div>
    <div class="dropdown more" hidden>
      <button class="btn dropdown-toggle" type="button" data-bs-toggle="dropdown" data-bs-offset="0,6" aria-expanded="false">More</button>
      <ul class="dropdown-menu dropdown-menu-end"></ul>
    </div>
  </div>
  <div class="tab-content">
    <div class="tab-pane active" id="ws-panel-0" role="tabpanel" aria-labelledby="ws-tab-0" tabindex="0">Everything at a glance — the tab that never gets folded away, because it is first.</div>
    <div class="tab-pane" id="ws-panel-1" role="tabpanel" aria-labelledby="ws-tab-1" tabindex="0">Every action taken in this workspace, newest first.</div>
    <div class="tab-pane" id="ws-panel-2" role="tabpanel" aria-labelledby="ws-tab-2" tabindex="0">Twelve people, three of them administrators.</div>
    <div class="tab-pane" id="ws-panel-3" role="tabpanel" aria-labelledby="ws-tab-3" tabindex="0">Connected tools and the scopes each was granted.</div>
    <div class="tab-pane" id="ws-panel-4" role="tabpanel" aria-labelledby="ws-tab-4" tabindex="0">Who can see what, and who is allowed to change it.</div>
    <div class="tab-pane" id="ws-panel-5" role="tabpanel" aria-labelledby="ws-tab-5" tabindex="0">Plan, invoices and the card on file.</div>
  </div>
</div>
CSS
/* Overflow tabs: Bootstrap .nav-underline with the Tab plugin, plus a Bootstrap dropdown that collects
   the tabs that do not fit. A short script measures the row; the selected tab is never folded away. */
.w{width:100%;max-width:600px;padding:24px 26px 26px;background:#fff;border:1px solid #e3e8f2;border-radius:16px;
  box-shadow:0 16px 38px -24px rgba(16,24,48,.24)}
.hd h2{font-size:1rem;font-weight:700;line-height:normal;margin:0 0 3px}
.hd p{font-size:.82rem;color:#5d6a86;margin:0 0 16px}
.bar{display:flex;align-items:center;gap:4px;border-bottom:1px solid #e6eaf3;margin-bottom:18px}
.tabs{--bs-nav-underline-gap:4px;--bs-nav-underline-border-width:2px;--bs-nav-underline-link-active-color:#4f46e5;
  --bs-nav-link-color:#5d6a86;--bs-nav-link-hover-color:#16203a;--bs-nav-link-font-size:.87rem;--bs-nav-link-font-weight:700;
  flex:1;min-width:0;flex-wrap:nowrap}
.tabs .nav-link{padding:10px 13px;margin-bottom:-1px;border-radius:8px 8px 0 0;white-space:nowrap}
.tabs .nav-link:hover{background:#f6f8fc}
.tabs .nav-link:not(.active):hover,.tabs .nav-link:not(.active):focus{border-bottom-color:transparent}
.tabs .nav-link:focus-visible{box-shadow:none;outline:2px solid #4f46e5;outline-offset:-2px}
.more{flex-shrink:0}
.more .btn{--bs-btn-padding-x:12px;--bs-btn-padding-y:10px;--bs-btn-font-size:.87rem;--bs-btn-font-weight:700;--bs-btn-line-height:normal;
  --bs-btn-color:#5d6a86;--bs-btn-border-width:0;--bs-btn-border-radius:8px;--bs-btn-hover-bg:#f6f8fc;--bs-btn-hover-color:#16203a;
  --bs-btn-active-bg:#f6f8fc;--bs-btn-active-color:#16203a;--bs-btn-focus-box-shadow:none}
.more .btn:focus-visible{outline:2px solid #4f46e5;outline-offset:-2px}
.more .dropdown-menu{--bs-dropdown-min-width:170px;--bs-dropdown-padding-x:6px;--bs-dropdown-padding-y:6px;--bs-dropdown-font-size:.87rem;
  --bs-dropdown-border-color:#e3e8f2;--bs-dropdown-border-radius:11px;--bs-dropdown-item-padding-x:13px;--bs-dropdown-item-padding-y:10px;
  --bs-dropdown-item-border-radius:8px;--bs-dropdown-link-color:#5d6a86;--bs-dropdown-link-hover-color:#16203a;--bs-dropdown-link-hover-bg:#f6f8fc;
  box-shadow:0 16px 34px -18px rgba(16,24,48,.3)}
.more .dropdown-item{font-weight:700}
.more .dropdown-item:focus-visible{outline:2px solid #4f46e5;outline-offset:-2px}
.w .tab-pane{font-size:.89rem;line-height:1.7;color:#4a5670;border-radius:4px}
.w .tab-pane:focus-visible{outline:2px solid #4f46e5;outline-offset:4px}
JavaScript
// Fold the tabs that do not fit into the More dropdown. Folded tabs are hidden and disabled,
// so the Tab plugin's arrow keys skip them; the dropdown items show them through bootstrap.Tab.
const list = document.querySelector('.bar [role=tablist]');
const more = document.querySelector('.bar .more');
const menu = more.querySelector('.dropdown-menu');
const tabs = [...list.querySelectorAll('[role=tab]')];

const layout = () => {
  tabs.forEach(t => { t.hidden = false; t.disabled = false; });
  more.hidden = true;
  const folded = [];
  // Never fold the first tab or the selected one.
  for (let i = tabs.length - 1; i > 0 && list.scrollWidth > list.clientWidth; i--) {
    if (tabs[i].classList.contains('active')) continue;
    tabs[i].hidden = true;
    tabs[i].disabled = true;
    folded.unshift(tabs[i]);
    more.hidden = false;
  }
  menu.replaceChildren(...folded.map(t => {
    const li = document.createElement('li');
    const b = document.createElement('button');
    b.type = 'button';
    b.className = 'dropdown-item';
    b.textContent = t.textContent;
    b.addEventListener('click', () => {
      t.hidden = false;
      t.disabled = false;
      bootstrap.Tab.getOrCreateInstance(t).show();
      t.focus();
    });
    li.append(b);
    return li;
  }));
};

tabs.forEach(t => t.addEventListener('shown.bs.tab', layout));
new ResizeObserver(layout).observe(list.parentElement);
layout();

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div class="w-full max-w-[600px] 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><h2 class="mb-[3px] text-base font-bold">Nav overflow</h2><p class="mb-4 text-[.82rem] text-tab-muted">Narrow the window — the tabs that stop fitting move into More.</p></div>
  <div class="mb-[18px] flex items-center gap-1 border-b border-tab-rule" data-overflow-bar>
    <div class="flex min-w-0 flex-1 gap-1" role="tablist" aria-label="Workspace">
      <button type="button" role="tab" id="ws-tab-0" aria-controls="ws-panel-0" aria-selected="true" class="-mb-px cursor-pointer rounded-t-lg border-b-2 border-transparent px-[13px] py-2.5 text-[.87rem] font-bold whitespace-nowrap text-tab-muted hover:bg-tab-hover hover:text-tab-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tab-accent aria-selected:border-tab-accent aria-selected:text-tab-accent">Overview</button>
      <button type="button" role="tab" id="ws-tab-1" aria-controls="ws-panel-1" aria-selected="false" tabindex="-1" class="-mb-px cursor-pointer rounded-t-lg border-b-2 border-transparent px-[13px] py-2.5 text-[.87rem] font-bold whitespace-nowrap text-tab-muted hover:bg-tab-hover hover:text-tab-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tab-accent aria-selected:border-tab-accent aria-selected:text-tab-accent">Activity</button>
      <button type="button" role="tab" id="ws-tab-2" aria-controls="ws-panel-2" aria-selected="false" tabindex="-1" class="-mb-px cursor-pointer rounded-t-lg border-b-2 border-transparent px-[13px] py-2.5 text-[.87rem] font-bold whitespace-nowrap text-tab-muted hover:bg-tab-hover hover:text-tab-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tab-accent aria-selected:border-tab-accent aria-selected:text-tab-accent">Members</button>
      <button type="button" role="tab" id="ws-tab-3" aria-controls="ws-panel-3" aria-selected="false" tabindex="-1" class="-mb-px cursor-pointer rounded-t-lg border-b-2 border-transparent px-[13px] py-2.5 text-[.87rem] font-bold whitespace-nowrap text-tab-muted hover:bg-tab-hover hover:text-tab-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tab-accent aria-selected:border-tab-accent aria-selected:text-tab-accent">Integrations</button>
      <button type="button" role="tab" id="ws-tab-4" aria-controls="ws-panel-4" aria-selected="false" tabindex="-1" class="-mb-px cursor-pointer rounded-t-lg border-b-2 border-transparent px-[13px] py-2.5 text-[.87rem] font-bold whitespace-nowrap text-tab-muted hover:bg-tab-hover hover:text-tab-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tab-accent aria-selected:border-tab-accent aria-selected:text-tab-accent">Permissions</button>
      <button type="button" role="tab" id="ws-tab-5" aria-controls="ws-panel-5" aria-selected="false" tabindex="-1" class="-mb-px cursor-pointer rounded-t-lg border-b-2 border-transparent px-[13px] py-2.5 text-[.87rem] font-bold whitespace-nowrap text-tab-muted hover:bg-tab-hover hover:text-tab-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tab-accent aria-selected:border-tab-accent aria-selected:text-tab-accent">Billing</button>
    </div>
    <details class="relative shrink-0" hidden><summary class="cursor-pointer list-none rounded-lg px-3 py-2.5 text-[.87rem] font-bold whitespace-nowrap text-tab-muted hover:bg-tab-hover hover:text-tab-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tab-accent [&::-webkit-details-marker]:hidden">More <span aria-hidden="true">▾</span></summary>
      <div class="absolute top-[calc(100%+6px)] right-0 z-[5] min-w-[170px] rounded-[11px] border border-tab-line bg-white p-1.5 shadow-[0_16px_34px_-18px_rgba(16,24,48,.3)] *:block *:w-full *:cursor-pointer *:rounded-lg *:px-[13px] *:py-2.5 *:text-left *:text-[.87rem] *:font-bold *:whitespace-nowrap *:text-tab-muted *:hover:bg-tab-hover *:hover:text-tab-ink *:focus-visible:outline-2 *:focus-visible:-outline-offset-2 *:focus-visible:outline-tab-accent" data-menu></div>
    </details>
  </div>
  <div id="ws-panel-0" role="tabpanel" aria-labelledby="ws-tab-0" tabindex="0" class="rounded text-[.89rem] leading-[1.7] text-tab-body focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tab-accent">Everything at a glance — the tab that never gets folded away, because it is first.</div>
  <div id="ws-panel-1" role="tabpanel" aria-labelledby="ws-tab-1" tabindex="0" hidden class="rounded text-[.89rem] leading-[1.7] text-tab-body focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tab-accent">Every action taken in this workspace, newest first.</div>
  <div id="ws-panel-2" role="tabpanel" aria-labelledby="ws-tab-2" tabindex="0" hidden class="rounded text-[.89rem] leading-[1.7] text-tab-body focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tab-accent">Twelve people, three of them administrators.</div>
  <div id="ws-panel-3" role="tabpanel" aria-labelledby="ws-tab-3" tabindex="0" hidden class="rounded text-[.89rem] leading-[1.7] text-tab-body focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tab-accent">Connected tools and the scopes each was granted.</div>
  <div id="ws-panel-4" role="tabpanel" aria-labelledby="ws-tab-4" tabindex="0" hidden class="rounded text-[.89rem] leading-[1.7] text-tab-body focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tab-accent">Who can see what, and who is allowed to change it.</div>
  <div id="ws-panel-5" role="tabpanel" aria-labelledby="ws-tab-5" tabindex="0" hidden class="rounded text-[.89rem] leading-[1.7] text-tab-body focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tab-accent">Plan, invoices and the card on file.</div>
</div>
CSS
@theme {
  --color-tab-ink: #16203a;
  --color-tab-body: #4a5670;
  --color-tab-muted: #5d6a86;
  --color-tab-line: #e3e8f2;
  --color-tab-rule: #e6eaf3;
  --color-tab-hover: #f6f8fc;
  --color-tab-accent: #4f46e5;
}
JavaScript
// Tabs that fold into a More menu. The fitting depends on measured widths, which CSS cannot do:
// tabs are hidden from the end until the row fits, and each hidden tab gets a button in the menu.
// Keyboard: arrow keys move between the visible tabs, Home/End jump to the ends, Escape closes the menu.
const bar = document.querySelector('[data-overflow-bar]');
const list = bar.querySelector('[role=tablist]');
const more = bar.querySelector('details');
const menu = more.querySelector('[data-menu]');
const tabs = [...list.querySelectorAll('[role=tab]')];
const isSelected = t => t.getAttribute('aria-selected') === 'true';

const layout = () => {
  tabs.forEach(t => { t.hidden = false; });
  more.hidden = true;
  const folded = [];
  // Never fold the first tab or the selected one.
  for (let i = tabs.length - 1; i > 0 && list.scrollWidth > list.clientWidth; i--) {
    if (isSelected(tabs[i])) continue;
    tabs[i].hidden = true;
    folded.unshift(tabs[i]);
    more.hidden = false;
  }
  menu.replaceChildren(...folded.map(t => {
    const b = document.createElement('button');
    b.type = 'button';
    b.textContent = t.textContent;
    b.dataset.tab = t.id;
    return b;
  }));
  if (!folded.length) more.open = false;
};

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;
  });
  layout();
};

list.addEventListener('click', e => { const t = e.target.closest('[role=tab]'); if (t) select(t); });
list.addEventListener('keydown', e => {
  const shown = tabs.filter(t => !t.hidden), i = shown.indexOf(e.target);
  const n = { ArrowLeft: i - 1, ArrowRight: i + 1, Home: 0, End: shown.length - 1 }[e.key];
  if (n === undefined || i < 0) return;
  e.preventDefault();
  const t = shown[(n + shown.length) % shown.length];
  t.focus();
  select(t);
});
menu.addEventListener('click', e => {
  const b = e.target.closest('button');
  if (!b) return;
  const tab = document.getElementById(b.dataset.tab);
  more.open = false;
  select(tab);
  tab.focus();
});
more.addEventListener('keydown', e => {
  if (e.key === 'Escape' && more.open) { more.open = false; more.querySelector('summary').focus(); }
});
document.addEventListener('click', e => { if (!more.contains(e.target)) more.open = false; });

new ResizeObserver(layout).observe(bar);
layout();

About this set

An underlined tab bar that measures itself and folds the tabs that do not fit into a More menu at the right, the way app and admin navigation behaves on narrow screens. It has six tabs: Overview, Activity, Members, Integrations, Permissions and Billing. Use it when the number of sections is fixed but the width is not, and a scrolling tab bar would hide options without saying so. A ResizeObserver reruns the fitting whenever the bar changes width: every tab is shown, then tabs are hidden from the end until the row fits, skipping the first tab and the selected one, and each hidden tab gets a button in the menu. Choosing a menu item selects that tab, moves it back into the bar and focuses it. The plain CSS and Tailwind versions use a details element for the menu; the Bootstrap version uses .nav-underline with the Tab plugin and a Bootstrap dropdown, and marks folded tabs as disabled so the Tab plugin’s arrow keys skip them.

What’s included

  • Tabs that do not fit fold into a More menu
  • The selected tab is never folded away
  • Refits on every width change with a ResizeObserver
  • Full WAI-ARIA tabs keyboard support over the visible tabs
  • Bootstrap version pairs .nav-underline with a dropdown

Accessibility

The bar follows the WAI-ARIA tabs pattern in all three versions: role tablist, tab and tabpanel, aria-selected, aria-controls, a roving tabindex, and arrow keys plus Home and End over the tabs that are visible. The More control sits outside the tablist; in the plain and Tailwind versions it is a details disclosure that closes with Escape and returns focus to its summary, and in Bootstrap it is a dropdown with Bootstrap's own keyboard handling. Tabs, menu items and panels show a 2px indigo outline on keyboard focus.

Customise it

The underline and selected colour are #4f46e5 in the .tabs button rules (plain CSS), --bs-nav-underline-link-active-color on .tabs (Bootstrap) or --color-tab-accent (Tailwind). The menu is styled in .pop, the --bs-dropdown-* variables on .dropdown-menu, or the utilities on the menu panel.