Tabs with `take`

A classic tab strip with one centralized handler on the parent. The `take` command moves the active state from sibling tabs to the clicked one in a single statement.

A classic tab strip - click a tab, that tab becomes active and its panel shows. One handler on the parent uses event delegation to catch any tab click, then take moves the aria-selected state to the clicked tab and add ... when toggles the hidden attribute on the panels.

Example: Click a tab
<div class="tabs" _="on click
    set tab to the closest <[role=tab]/> to the target
    if no tab exit end
    take @aria-selected='true' from <[role=tab]/> in me giving 'false' for tab
    add @hidden to <[role=tabpanel]/> in me when its @id is not (tab's @aria-controls)">

    <div class="tab-bar" role="tablist">
        <button class="tab" role="tab" aria-selected="true" aria-controls="panel-overview">Overview</button>
        <button class="tab" role="tab" aria-selected="false" aria-controls="panel-specs">Specs</button>
        <button class="tab" role="tab" aria-selected="false" aria-controls="panel-reviews">Reviews</button>
    </div>

    <div class="panel" id="panel-overview" role="tabpanel">
        <p>A medium-roast coffee with notes of caramel, dried fruit, and dark
        chocolate. Sourced from a small cooperative in Huila, Colombia.</p>
    </div>
    <div class="panel" id="panel-specs" role="tabpanel" hidden>
        <ul>
            <li>Origin: Huila, Colombia</li>
            <li>Roast: Medium</li>
            <li>Process: Washed</li>
            <li>Altitude: 1,650-1,950m</li>
        </ul>
    </div>
    <div class="panel" id="panel-reviews" role="tabpanel" hidden>
        <p><strong>★★★★★</strong> "My new daily driver." - Jamie</p>
        <p><strong>★★★★☆</strong> "Bright, clean, lovely finish." - Riley</p>
    </div>
</div>

A medium-roast coffee with notes of caramel, dried fruit, and dark chocolate. Sourced from a small cooperative in Huila, Colombia.

Try It!

The whole tab strip is one handler on the parent:

on click
  set tab to the closest <[role=tab]/> to the target
  if no tab exit end
  take @aria-selected='true' from <[role=tab]/> in me giving 'false' for tab
  add @hidden to <[role=tabpanel]/> in me when its @id is not (tab's @aria-controls)

Four lines, in order:

  1. set tab to the closest <[role=tab]/> to the target - find the tab the user clicked. the target is the click event's event.target, and closest walks up to find the nearest ancestor (or self) matching the selector. If they clicked outside any tab, tab is null.

  2. if no tab exit end - bail out cleanly when the click wasn't on a tab.

  3. take @aria-selected='true' from <[role=tab]/> in me giving 'false' for tab - the take shibboleth, in its richest form:

    • @aria-selected='true' is what gets added to the for target
    • from <[role=tab]/> in me is the source set, scoped to descendants of this tablist
    • giving 'false' is what the from elements get instead of having the attribute removed (giving is an alias for with, and reads more naturally in this position)
    • for tab is the destination

    Net effect: every tab in this component gets aria-selected="false", then the clicked tab gets aria-selected="true". The browser styles it via [aria-selected="true"] in CSS - no .active class needed.

  4. add @hidden to <[role=tabpanel]/> in me when its @id is not (tab's @aria-controls) - for each tabpanel, add @hidden if its id doesn't match the active tab's aria-controls, and remove it if it does. add ... when toggles in both directions, so one statement handles all panels at once.

Why ARIA, not .active

This is the rare case where the accessible markup is also the shorter markup: one handler, two statements, zero JS classes to manage.

Why take shines here

The vanilla equivalent of just the active-tab toggle is six lines:

document.querySelectorAll('[role=tab]').forEach(t => t.setAttribute('aria-selected', 'false'));
clickedTab.setAttribute('aria-selected', 'true');
document.querySelectorAll('[role=tabpanel]').forEach(p => p.hidden = true);
document.getElementById(clickedTab.getAttribute('aria-controls')).hidden = false;

Hyperscript collapses each forEach + set into one statement because that's the operation take was named for: take this attribute (with this value) away from a group, give it to one element. Tabs, segmented controls, "selected row" highlights, mode switches, breadcrumb-style step indicators - any mutually-exclusive UI state is a take away.

Heads up: the component version of this pattern lives at Tab Set Component, which wraps the same logic into a <tab-set> custom element.