What it does

A PanelSet switches panels, but it does not automatically listen to the clicks on your tabs. On the PanelSet example pages that is all done with JavaScript, an extra script on each page. If you just read the docs of PanelSet, you might think you have to write that code yourself.

PanelControl to the rescue: assign it to the element that holds your triggers and it activates the right panel on click, adds keyboard navigation, and reflects state back onto the controls.

It finds its PanelSet through the panels the triggers point at, so the control can live anywhere in the DOM. It does not need to sit next to the PanelSet. One PanelControl drives one PanelSet.

Setup

PanelControl ships inside the panelset package, so there is nothing extra to install.

Installing PanelControl

Terminal
npm install panelset

Using PanelControl

With the script-tag build, window.PanelControl is available and the custom elements are already registered. See the Web Component page to learn more.

Markup

Put data-panelcontrol on the element holding your triggers. Each trigger points at a panel with aria-controls. Add role="tablist" (with role="tab" on the buttons) to switch on keyboard (arrow) navigation.

Key Features

  • This is our most important feature ever!
HTML
<div class="tabs" role="tablist" data-panelcontrol>
	<button role="tab" aria-controls="markup-panel-1" aria-selected="true">Features</button>
	<button role="tab" aria-controls="markup-panel-2">Pricing</button>
	<button role="tab" aria-controls="markup-panel-3">Support</button>
</div>

<div class="tab-content" data-panelset>
	<div class="panel-wrapper">
		<div class="active" id="markup-panel-1" role="tabpanel">…</div>
		<div id="markup-panel-2" role="tabpanel" hidden>…</div>
		<div id="markup-panel-3" role="tabpanel" hidden>…</div>
	</div>
</div>

Initializing

Initialise PanelSet and PanelControl. Order does not matter (PanelControl resolves its PanelSet lazily).

JS
import { PanelSet, PanelControl } from 'panelset';

PanelSet.init();
PanelControl.init();            // every [data-panelcontrol]
PanelControl.init('#my-tabs');  // or a specific control

PanelSet is required. PanelControl drives a PanelSet. It does not replace it, and it holds no panel-switching logic of its own. With no PanelSet initialised, clicks do nothing (in debug mode PanelControl logs why). For plain tabs with no animation, keep PanelSet and turn transitions off: PanelSet.init({ transitions: false }).

Layout patterns

The control’s role decides its keyboard model (and how much ARIA you take on).

Horizontal tablist
role="tablist" with role="tab" children. Left / Right arrows, Home / End, roving tabindex. The default, best-supported pattern.
Vertical tablist
The same, plus aria-orientation="vertical": arrows become Up / Down; everything else is identical. CSS lays it out as a sidebar. Covers most "sidebar that switches panels" needs, with no extra ARIA.

See Activation, Closable, and Vertical display for worked examples.

Scope

PanelControl manages controls and reflects state: activation, keyboard nav, roving tabindex, selected state (aria-selected on tabs, aria-current otherwise) / disabled. It also exposes setTabState(id, 'disabled' | 'enabled') so external code can lock or unlock a tab; PanelControl just applies it.

Validation, Previous / Next flow, step locking, and persistence are not part of PanelControl. Those are up to your own code, which calls setTabState when a step should lock. The direction is one way: your code calls PanelControl, which calls PanelSet, never the reverse.

Next: every option in Configuration, and the method list in the API reference.