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