NETFRAME_DOCS // TABS
GITHUB

Tabs

Horizontal tab navigation with associated content panels. Manage active state via aria-selected and hidden.

Basic

TABS

System overview data goes here.
<div class="nf-tabs">
<div class="nf-tab-list" role="tablist" aria-label="System modules">
  <button class="nf-tab" role="tab" aria-selected="true" aria-controls="tab-panel-1" id="tab-1">OVERVIEW</button>
  <button class="nf-tab" role="tab" aria-selected="false" aria-controls="tab-panel-2" id="tab-2">NETWORK</button>
  <button class="nf-tab" role="tab" aria-selected="false" aria-controls="tab-panel-3" id="tab-3">SECURITY</button>
  <button class="nf-tab" role="tab" aria-selected="false" aria-controls="tab-panel-4" id="tab-4" aria-disabled="true">ARCHIVE</button>
</div>
<div class="nf-tab-panel" role="tabpanel" id="tab-panel-1" aria-labelledby="tab-1">
  System overview data goes here.
</div>
<div class="nf-tab-panel" role="tabpanel" id="tab-panel-2" aria-labelledby="tab-2" hidden>
  Network topology data goes here.
</div>
<div class="nf-tab-panel" role="tabpanel" id="tab-panel-3" aria-labelledby="tab-3" hidden>
  Security logs go here.
</div>
<div class="nf-tab-panel" role="tabpanel" id="tab-panel-4" aria-labelledby="tab-4" hidden>
  Archive data goes here.
</div>
</div>

Secondary color

SECONDARY VARIANT

<div class="nf-tabs nf-tabs-secondary">
<div class="nf-tab-list" role="tablist" aria-label="Link channels">
  <button class="nf-tab" role="tab" aria-selected="true" aria-controls="link-panel-1" id="link-tab-1">UPLINK</button>
  <button class="nf-tab" role="tab" aria-selected="false" aria-controls="link-panel-2" id="link-tab-2">DOWNLINK</button>
  <button class="nf-tab" role="tab" aria-selected="false" aria-controls="link-panel-3" id="link-tab-3">BYPASS</button>
</div>
<div class="nf-tab-panel" role="tabpanel" id="link-panel-1" aria-labelledby="link-tab-1">Uplink active. Outbound packets are queued for transmission.</div>
<div class="nf-tab-panel" role="tabpanel" id="link-panel-2" aria-labelledby="link-tab-2" hidden>Downlink connected. Incoming telemetry is synchronized.</div>
<div class="nf-tab-panel" role="tabpanel" id="link-panel-3" aria-labelledby="link-tab-3" hidden>Bypass channel ready. Traffic follows the direct route.</div>
</div>

JavaScript

document.querySelectorAll('.nf-tab-list').forEach((list) => {
  const tabs = [...list.querySelectorAll('.nf-tab')];
  const enabled = () => tabs.filter((t) => t.getAttribute('aria-disabled') !== 'true' && !t.disabled);

  function select(tab, { focus = false } = {}) {
    if (!tab || tab.getAttribute('aria-disabled') === 'true' || tab.disabled) return;
    tabs.forEach((t) => {
      const active = t === tab;
      t.setAttribute('aria-selected', String(active));
      t.tabIndex = active ? 0 : -1; // roving tabindex: one tab stop per list
      document.getElementById(t.getAttribute('aria-controls'))?.toggleAttribute('hidden', !active);
    });
    if (focus) tab.focus();
  }

  select(enabled().find((t) => t.getAttribute('aria-selected') === 'true') ?? enabled()[0]);

  list.addEventListener('click', (e) => {
    const tab = e.target.closest('.nf-tab');
    if (tab) select(tab);
  });

  // Arrow keys / Home / End move between enabled tabs (automatic activation).
  list.addEventListener('keydown', (e) => {
    const order = enabled();
    const i = order.indexOf(document.activeElement);
    const next = {
      ArrowRight: order[(i + 1) % order.length],
      ArrowLeft: order[(i - 1 + order.length) % order.length],
      Home: order[0],
      End: order[order.length - 1],
    }[e.key];
    if (next) { e.preventDefault(); select(next, { focus: true }); }
  });
});

Class reference

Class Description
nf-tabs Outer wrapper
nf-tab-list Horizontal row of tab buttons
nf-tab Individual tab button — set aria-selected="true" for active
nf-tab-active Class alternative to aria-selected for active state
nf-tabs-secondary Secondary magenta active underline
nf-tab-panel Content panel — hide inactive panels with hidden