v1.6.x-dev NDS IQ v6

Content Switcher - National Design System

Content switcher allows users to toggle between different content sections within the same space on the screen

Standard

The default strip. Reach for it when the choices are few, short, and equal in weight, and you want the current one to read at a glance.

Every request, unfiltered. Start here when you are scanning for anything that needs attention.

<div class="nds-tabs nds-content-switcher" id="switcher-standard-1"> <div class="nds-tab-list" role="tablist" aria-label="Request status"> <button class="nds-btn nds-secondary nds-tab" type="button" role="tab" aria-selected="true" aria-controls="panel-switcher-standard-all" id="tab-switcher-standard-all" tabindex="0"> <span class="nds-label">All</span> </button> <button class="nds-btn nds-secondary nds-tab" type="button" role="tab" aria-selected="false" aria-controls="panel-switcher-standard-review" id="tab-switcher-standard-review" tabindex="-1"> <span class="nds-label">In Review</span> </button> <button class="nds-btn nds-secondary nds-tab" type="button" role="tab" aria-selected="false" aria-controls="panel-switcher-standard-done" id="tab-switcher-standard-done" tabindex="-1"> <span class="nds-label">Completed</span> </button> <button class="nds-btn nds-secondary nds-tab" type="button" role="tab" aria-selected="false" aria-controls="panel-switcher-standard-archived" id="tab-switcher-standard-archived" tabindex="-1"> <span class="nds-label">Archived</span> </button> </div> <div class="nds-tab-content"> <div class="nds-tab-panel" role="tabpanel" id="panel-switcher-standard-all" aria-labelledby="tab-switcher-standard-all" tabindex="0"> <p>Every request, unfiltered. Start here when you are scanning for anything that needs attention.</p> </div> <div class="nds-tab-panel" role="tabpanel" id="panel-switcher-standard-review" aria-labelledby="tab-switcher-standard-review" tabindex="-1" aria-hidden="true" hidden> <p>Requests still sitting with the reviewing authority. No action is required from you yet.</p> </div> <div class="nds-tab-panel" role="tabpanel" id="panel-switcher-standard-done" aria-labelledby="tab-switcher-standard-done" tabindex="-1" aria-hidden="true" hidden> <p>Requests that have been decided. The outcome and the decision date are on each record.</p> </div> <div class="nds-tab-panel" role="tabpanel" id="panel-switcher-standard-archived" aria-labelledby="tab-switcher-standard-archived" tabindex="-1" aria-hidden="true" hidden> <p>Requests closed more than a year ago, kept for reference and excluded from the other views.</p> </div> </div> </div>

Built-in Features

Auto-initialization

Activates on any switcher in the page. No setup call, no configuration object.

Panel Switching

Picking a segment reveals its panel and hides the rest, and fires an event you can listen for.

Keyboard Navigation

Arrow keys walk the strip, Home and End jump to the ends, Enter or Space commits the choice.

Three Size Steps

32, 40, and 48px strips, with label size, inline padding, and corner radius all following the step.

Dark Surface Support

The current segment turns brand green on dark backgrounds, in dark mode automatically or in light mode with one class.

RTL Aware

Rounded ends, seams, and arrow-key direction all mirror with text direction. No extra markup.

Loading Skeleton

Labels render as animated bars while data is in flight, and before the component's own script arrives.

Programmatic Control

Read or change the current segment from script, and re-scan the page after injecting new markup.

Usage Guidelines

Best Practices

  • Use a content switcher for two to four short, equal-weight views of the same subject: a date range, a status filter, a chart granularity
  • Use it when the current choice must stay obvious at a glance. The solid fill reads faster than an underline across a dense page
  • Do not use it for page-level navigation between unrelated areas. Use Tabs, which handle longer labels, icons, vertical layouts, and overflow scrolling
  • Do not use it as a form input. Segments are tabs, not controls: no name, no value, nothing submitted. For a bounded choice inside a form use Radio, which looks different but is the only NDS control that submits a single choice
  • Do not use it as a toolbar of actions. Segments select a view, they do not run commands. Use a plain Button group for that
  • Keep labels to one or two words. The strip does not scroll, so long labels either crowd the row or push it past its container
  • Keep the segment count stable. A strip whose options come and go is a filter, not a switcher
  • Pick the size from the surrounding density: nds-sm inside cards and toolbars, the default in page content, nds-lg for touch-first layouts
  • Mark the starting segment in your HTML with aria-selected="true" and give its panel no hidden attribute, so the correct view paints before scripts run
  • Every segment needs a panel. A switcher with no panels does not initialize; for a panel-free view toggle use a button group and handle clicks yourself

Modifier Classes

ClassDescription
nds-content-switcherCo-class on the .nds-tabs root. Turns the tab strip into a segmented control
nds-centerOn .nds-tab-list. Centers the strip in its row instead of aligning it to the inline start
nds-oncolorOn the root. Applies the dark-surface palette in light mode, for a strip placed on a dark or brand-filled panel
nds-smOn the root. 32px segments with the small type step and a tighter corner radius
nds-lgOn the root. 48px segments. The unmodified default is 40px, so the middle step needs no class
nds-loadingOn the root or the strip. Renders labels as animated bars. Equivalent to data-state="loading"

Data Attributes

AttributeDescription
aria-selected="true"Set on one segment to mark the starting choice. Its panel must not carry hidden. Every other segment takes aria-selected="false" and tabindex="-1", and its panel takes hidden
aria-controlsSet on each segment to the id of its panel. Panels pair back with aria-labelledby
data-state="loading"Set on the root or the strip to show the skeleton while data loads. Equivalent to the nds-loading class
data-nds-tabs-initializedStamped on the root by JS once wired. Until it lands the skeleton paints, so the strip is never bare

CSS Custom Properties

PropertyDefaultDescription
--btn-group-radiusvar(--radius-md)Corner radius of the strip's outer ends. Set on .nds-tab-list. Drops to var(--radius-sm) at nds-sm
--btn-size40pxSegment height. Set on .nds-tab for a size between the three steps
--tab-button-padding-inlinethe button's own paddingHorizontal padding inside each segment. Tracks the size step unless you override it
--tab-panel-paddingvar(--spacing-2xl)Padding around panel content. Axial --tab-panel-padding-inline and --tab-panel-padding-block override one side each

JavaScript API

The switcher is driven by NDS.Tabs, the same controller behind Tabs. It wires itself on page load. Call NDS.Tabs.reinit() after injecting a switcher into the page.

// ── Reach the instance ─────────────────────────────── // Parked on the root element once initialized const switcher = document.querySelector('#switcher-standard-1').ndsTabs; // ── Read the current segment ───────────────────────── switcher.getActiveTabIndex(); // 0-based index switcher.getActiveTab(); // the segment button element switcher.getActivePanel(); // the visible panel element // ── Change the current segment ─────────────────────── switcher.switchTo(2); // reveals the third panel, fires nds:tab:change // ── Listen for changes ─────────────────────────────── // Bubbles from the root, so one listener can cover several switchers document.querySelector('#switcher-standard-1').addEventListener('nds:tab:change', (e) => { e.detail.tabIndex; // index of the new segment e.detail.tab; // the new segment button e.detail.panel; // the newly visible panel e.detail.previousTab; // the segment being left e.detail.previousPanel; // the panel being hidden }); // ── Re-scan after injecting markup ─────────────────── // Skips switchers that are already wired NDS.Tabs.reinit(); // ── Tear down ──────────────────────────────────────── // Removes listeners and the init stamp, leaving markup in place switcher.destroy();
Last Modified Date: 29/07/2026 - 09:08 PM
Was this page useful?
60% of users said Yes from 2843 Feedbacks