Tabs
tier 2 — classes.tabs swaps a region of content in place. A strip that changes a
setting is the segmented control, and a strip of links to
other addresses only looks like tabs — the last example.
role="tablist",
role="tab", aria-selected and aria-controls, plus
arrow-key movement between them — data-tabs supplies the movement, and the
markup supplies the rest.
Tabs
The demo is live. data-tabs on the container hands the strip
to sednaUi.tabs — clicking, arrow keys, Home/End, showing
the matching aria-controls panel, and the roving tabindex that
makes Tab step over the whole tablist. Mark the selected tab with
aria-selected="true" rather than .tab--active, which is for a
strip that is genuinely not a tablist. An app driving its tabs from C# uses
data-tabs="managed" instead — below.
Twelve orders are waiting on a first response.
Three are yours: ORD-4209, ORD-4244 and ORD-4250.
Everything in the queue, decided or not.
<div>
<div class="tabs" role="tablist" data-tabs aria-label="Order views">
<button class="tab" role="tab" aria-selected="true" id="t-open" aria-controls="p-open" type="button">
<i class="ri-inbox-line"></i> Open <span class="tab-count">12</span>
</button>
<button class="tab" role="tab" aria-selected="false" id="t-mine" aria-controls="p-mine" type="button" tabindex="-1">
Assigned to me <span class="tab-count">3</span>
</button>
<button class="tab" role="tab" aria-selected="false" id="t-all" aria-controls="p-all" type="button" tabindex="-1">
Everything
</button>
<button class="tab" role="tab" aria-selected="false" aria-disabled="true" disabled type="button" tabindex="-1">
Archive
</button>
</div>
<div class="tab-panel" role="tabpanel" id="p-open" aria-labelledby="t-open">
<p class="lede">Twelve orders are waiting on a first response.</p>
</div>
<div class="tab-panel" role="tabpanel" id="p-mine" aria-labelledby="t-mine" hidden>
<p class="lede">Three are yours: ORD-4209, ORD-4244 and ORD-4250.</p>
</div>
<div class="tab-panel" role="tabpanel" id="p-all" aria-labelledby="t-all" hidden>
<p class="lede">Everything in the queue, decided or not.</p>
</div>
</div>
Tabs the app selects
Live — use the arrow keys. With data-tabs="managed" the script
owns only the keyboard: an arrow, Home or End moves focus to a tab and
clicks it, and your click handler changes the selection. Nothing the script does writes
aria-selected, tabindex or hidden, so there is one
writer. SednaTabs.Tab(key, selected, prefix) and Panel(…) render
those attributes, with ids that name each other; give each tablist on a page its own
prefix.
Twelve orders are waiting on a reservation.
Three orders are held for stock.
Forty-one orders left the warehouse today.
<div class="tabs" role="tablist" aria-label="Orders" data-tabs="managed">
@foreach (var tab in Tabs)
{
var key = tab.Key;
<button class="tab" type="button" @attributes="SednaTabs.Tab(key, _tab, Prefix)" @onclick="@(() => _tab = key)">
@tab.Label
</button>
}
</div>
@foreach (var tab in Tabs)
{
<div class="tab-panel" @attributes="SednaTabs.Panel(tab.Key, _tab, Prefix)">
<p>@tab.Body</p>
</div>
}
@code {
private const string Prefix = "orders";
private static readonly (string Key, string Label, string Body)[] Tabs =
[
("open", "Open", "Twelve orders are waiting on a reservation."),
("held", "Held", "Three orders are held for stock."),
("done", "Dispatched", "Forty-one orders left the warehouse today."),
];
// The selected tab is the app's state. data-tabs="managed" hands the keyboard to the
// script, which moves focus and clicks; the click handler above changes this, and
// SednaTabs renders what follows from it.
private string _tab = "open";
}
Tabs in a card
Live — switch between them. The tablist goes in
.card-head and the panels in the body.
Add .card-head--tabs to the head: it hands the head's padding
to the strip, so the head's own border becomes the strip's line and the active tab's
indicator meets the body edge — a plain .card-head draws two lines and anchors
the indicator to nothing.
| Item | Qty | Reserved |
|---|---|---|
| Desk lamp, matte | 2 | 2 |
| Cable tray, 600 mm | 1 | 1 |
| Monitor arm, dual | 2 | 0 |
- 09:15 Three of five lines reserved automatically.
- 09:18 Held for a decision by Alex Fischer.
<div class="card" style="max-width:520px">
<div class="card-head card-head--tabs">
<div class="tabs" role="tablist" data-tabs aria-label="ORD-4209 detail">
<button class="tab" role="tab" aria-selected="true" id="c-lines" aria-controls="cp-lines" type="button">
Lines <span class="tab-count">5</span>
</button>
<button class="tab" role="tab" aria-selected="false" id="c-history" aria-controls="cp-history" type="button" tabindex="-1">
History
</button>
</div>
</div>
<div class="card-body">
<div class="tab-panel" role="tabpanel" id="cp-lines" aria-labelledby="c-lines">
<table class="table">
<thead>
<tr><th>Item</th><th>Qty</th><th class="col-num">Reserved</th></tr>
</thead>
<tbody>
<tr><td>Desk lamp, matte</td><td>2</td><td class="col-num">2</td></tr>
<tr><td>Cable tray, 600 mm</td><td>1</td><td class="col-num">1</td></tr>
<tr><td>Monitor arm, dual</td><td>2</td><td class="col-num">0</td></tr>
</tbody>
</table>
</div>
<div class="tab-panel" role="tabpanel" id="cp-history" aria-labelledby="c-history" hidden>
<ol class="timeline">
<li>
<span class="timeline-when">09:15</span>
<span class="timeline-what">Three of five lines reserved automatically.</span>
</li>
<li>
<span class="timeline-when">09:18</span>
<span class="timeline-what">Held for a decision by Alex Fischer.</span>
</li>
</ol>
</div>
</div>
</div>
A strip of links
The same look over a row of <a>s to other addresses — a record's
sections, each a page of its own. This is not a tablist: there is no panel to swap and no
arrow-key contract, so it is a <nav> with an aria-label,
.tab--active marks the current page and aria-current="page" is
what announces it, as ActiveLink writes both. Leave role and
data-tabs off.
<nav class="tabs" aria-label="ORD-4209">
<a class="tab tab--active" href="#" aria-current="page">Summary</a>
<a class="tab" href="#">Lines <span class="tab-count">5</span></a>
<a class="tab" href="#">Shipments <span class="tab-count">2</span></a>
<a class="tab" href="#">History</a>
</nav>