David Darnes
Developer Advocate at zeroheight. Previously: Nordhealth, Ghost, Stackbit. Clients: Google, Buffer and Netlify. he/him
Using Web Components as part of a Design System
Developer Advocate at zeroheight
more…
W3C – Tabs Pattern
Inclusive Components – Tabbed Interfaces
CSS Tricks – Spicy Sections (ft. Dave Rupert)
Zach Leatherman – Seven minute tabs
Open UI – Tab Component Specification
tab
and tablist
rolestablist
using aria-label
aria-controls
and aria-labelledby
tabindex
for guiding focus between elements<div class="tabs">
<div role="tablist" aria-label="Tabs">
<button id="tab-1" role="tab" aria-selected="true" aria-controls="tabpanel-1" tabindex="0">
Tab 1
</button>
<button id="tab-2" role="tab" aria-selected="false" aria-controls="tabpanel-2" tabindex="-1">
Tab 2
</button>
<button id="tab-3" role="tab" aria-selected="false" aria-controls="tabpanel-3" tabindex="-1">
Tab 3
</button>
<button id="tab-4" role="tab" aria-selected="false" aria-controls="tabpanel-4" tabindex="-1">
Tab 4
</button>
</div>
<div id="tabpanel-1" role="tabpanel" aria-hidden="false" aria-labelledby="tab-1" tabindex="0">
Tab panel 1
</div>
<div id="tabpanel-2" role="tabpanel" aria-hidden="true" aria-labelledby="tab-2" tabindex="0">
Tab panel 2
</div>
<div id="tabpanel-3" role="tabpanel" aria-hidden="true" aria-labelledby="tab-3" tabindex="0">
Tab panel 3
</div>
<div id="tabpanel-4" role="tabpanel" aria-hidden="true" aria-labelledby="tab-4" tabindex="0">
Tab panel 4
</div>
</div>
<nord-tab-group label="Title">
<nord-tab slot="tab">Tab 1</nord-tab>
<nord-tab-panel>
Tab panel 1
</nord-tab-panel>
<nord-tab slot="tab">Tab 2</nord-tab>
<nord-tab-panel>
Tab panel 2
</nord-tab-panel>
<nord-tab slot="tab">Tab 3</nord-tab>
<nord-tab-panel>
Tab panel 3
</nord-tab-panel>
<nord-tab slot="tab">Tab 4</nord-tab>
<nord-tab-panel>
Tab panel 4
</nord-tab-panel>
</nord-tab-group>
import {html, css, LitElement} from 'lit';
export class SimpleGreeting extends LitElement {
static styles = css`p { color: blue }`;
static properties = {
name: {type: String},
};
constructor() {
super();
this.name = 'Somebody';
}
render() {
return html`<p>Hello, ${this.name}!</p>`;
}
}
customElements.define('simple-greeting', SimpleGreeting);
import {html, css, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';
@customElement('simple-greeting')
export class SimpleGreeting extends LitElement {
static styles = css`p { color: blue }`;
@property()
name = 'Somebody';
render() {
return html`<p>Hello, ${this.name}!</p>`;
}
}
private updateSelectedTab(selectedTab: Tab) {
const selectedPanel = this.querySelector(`#${selectedTab.getAttribute("aria-controls")}`)
if (selectedTab === this.selectedTab) return
/**
* Reset all the selected state of the tabs, and select the clicked tab
*/
this.querySelectorAll("nord-tab").forEach(tab => {
tab.removeAttribute("selected")
if (tab === selectedTab) {
tab.setAttribute("selected", "")
tab.focus()
tab.scrollIntoView({ block: "nearest", inline: "nearest" })
this.selectedTab = tab
}
})
/**
* Reset all the visibility of the panels,
* and show the panel related to the selected tab
*/
this.querySelectorAll("nord-tab-panel").forEach(panel => {
panel.setAttribute("aria-hidden", `${panel !== selectedPanel}`)
})
}
switch (event.key) {
case "ArrowLeft":
updateTab(this.direction.isLTR ? previousTab : nextTab, event)
break
case "ArrowRight":
updateTab(this.direction.isLTR ? nextTab : previousTab, event)
break
case "Home":
updateTab(firstTab, event)
break
case "End":
updateTab(lastTab, event)
break
default:
break
}
<nord-tab-group label="Title">
<nord-tab slot="tab">Tab 1</nord-tab>
<nord-tab-panel>
Tab panel 1
</nord-tab-panel>
<nord-tab slot="tab" selected>Tab 2</nord-tab>
<nord-tab-panel>
Tab panel 2
</nord-tab-panel>
</nord-tab-group>
mutations.forEach(mutation => {
if (mutation.attributeName === "selected" && mutation.oldValue === null) {
const selectedTab = mutation.target
this.observer.disconnect()
this.updateSelectedTab(selectedTab)
this.observer.observe(this, TabGroup.observerOptions)
}
})
Louis Lazaris – Getting To Know The MutationObserver API
<div class="n-tab" data-text="The tab label">
The tab label
</div>
.n-tab::before {
content: attr(data-text);
font-weight: var(--n-font-weight-active);
display: block;
block-size: 0;
visibility: hidden;
}
David Darnes – Building tabs in Web Components
Lea Verou – Pure CSS scrolling shadows
By David Darnes
Developer Advocate at zeroheight. Previously: Nordhealth, Ghost, Stackbit. Clients: Google, Buffer and Netlify. he/him