<tab-list>
<menu>
<li><button type="button" aria-pressed="true">Tab 1</button></li>
<li><button type="button">Tab 2</button></li>
<li><button type="button">Tab 3</button></li>
</menu>
<details open>
<summary>Tab 1</summary>
Tab 1 content
</details>
<details>
<summary>Tab 2</summary>
Tab 2 content
</details>
<details>
<summary>Tab 3</summary>
Tab 3 content
</details>
</tab-list>
tab-list {
display: block;
margin-bottom: var(--space-l);
> menu {
display: flex;
border-bottom: 1px solid var(--color-gray-50);
padding: 0;
margin-bottom: 0;
> li {
display: inline-block;
margin: 0;
padding: 0;
}
& button {
border: 0;
border-top: 2px solid transparent;
border-bottom-width: 0;
font-family: var(--font-family-sans);
font-size: var(--font-size-s);
font-weight: var(--font-weight-bold);
padding: var(--space-s) var(--space-m);
color: var(--color-text-soft);
background-color: var(--color-secondary);
cursor: pointer;
transition: all var(--transition-short) var(--easing-inout);
&:hover,
&:focus {
color: var(--color-text);
background-color: var(--color-secondary-hover);
}
&:active {
color: var(--color-text);
background-color: var(--color-secondary-active);
}
&[aria-pressed="true"] {
color: var(--color-primary-active);
border-top: 3px solid var(--color-primary);
background-color: var(--color-background);
margin-bottom: -1px;
}
}
}
> details {
font-family: sans-serif;
font-size: var(--font-size-m);
background: var(--color-background);
& summary {
cursor: pointer;
font-size: var(--font-size-m);
font-weight: var(--font-weight-bold);
margin: 0 0 var(--space-s);
}
::marker,
::-webkit-details-marker {
color: var(--color-text-soft);
}
&[open] {
padding-block: var(--space-m);
}
}
&[accordion] {
> menu {
display: none;
}
details[open] {
padding-top: 0;
}
}
&:not([accordion]) > details summary {
display: none;
}
}
import { UIElement, asBoolean, setAttribute, setProperty, toggleAttribute, toggleClass } from '../../../'
export class TabList extends UIElement<{
active: number,
accordion: boolean,
}> {
static readonly localName = 'tab-list'
static observedAttributes = ['accordion']
init = {
active: 0,
accordion: asBoolean,
}
connectedCallback() {
super.connectedCallback()
// Set inital active tab by querying details[open]
const getInitialActive = () => {
const panels = Array.from(this.querySelectorAll('details'))
for (let i = 0; i < panels.length; i++) {
if (panels[i].hasAttribute('open')) return i
}
return 0
}
this.set('active', getInitialActive())
// Reflect accordion attribute (may be used for styling)
this.self.sync(toggleAttribute('accordion'))
// Update active tab state and bind click handlers
this.all('menu button')
.on('click', (_, index) => () => {
this.set('active', index)
})
.sync(setProperty(
'ariaPressed',
(_, index) => String(this.get('active') === index)
))
// Update details panels open, hidden and disabled states
this.all<HTMLDetailsElement>('details').sync(
setProperty(
'open',
(_, index) => !!(this.get('active') === index)
),
setAttribute(
'aria-disabled',
() => String(!this.get('accordion'))
)
)
// Update summary visibility
this.all('summary').sync(toggleClass(
'visually-hidden',
() => !this.get('accordion')
))
}
}
TabList.define()