Tabs
Tabs are a set of layered sections of content that are displayed individually and toggled through.
Consequat sint dolor incididunt
Ex do nulla elit amet et laboris tempor qui nulla. Sunt amet voluptate enim dolore sit cupidatat. Incididunt ex non minim aliquip sunt eu. Est elit et eu enim sunt enim qui do adipisicing. Culpa veniam laboris nostrud tempor est minim irure fugiat sit aute laborum.
Back to tabs listLaboris aliquip
Veniam eu occaecat cillum ut anim esse minim anim veniam amet quis culpa excepteur aute. Nulla duis nostrud laborum Lorem esse ea consectetur sunt. Ut dolor eu sit ipsum Lorem enim non nisi incididunt in proident aliqua. Sunt cupidatat officia amet quis irure qui tempor ipsum labore deserunt occaecat enim labore id.
Back to tabs listAmet nisi labore
Aliqua occaecat voluptate laborum amet fugiat. Duis dolore exercitation id anim sit ut ipsum occaecat consectetur ullamco dolor consequat occaecat. Aute irure nisi sint minim id ad laboris et sunt do ad pariatur consectetur.
Back to tabs list<Tabs.Root> <Tabs.List> <Tabs.Trigger value="tab-1">Tab 1</Tabs.Trigger> <Tabs.Trigger value="tab-2">Tab 2</Tabs.Trigger> <Tabs.Trigger value="tab-3">Tab 3</Tabs.Trigger> </Tabs.List> <Tabs.Content value="tab-1"> <h1 class="text-xl font-bold">Consequat sint dolor incididunt</h1> <p>Ex do nulla elit amet et laboris tempor qui nulla. Sunt amet voluptate enim dolore sit cupidatat. Incididunt ex non minim aliquip sunt eu. Est elit et eu enim sunt enim qui do adipisicing. Culpa veniam laboris nostrud tempor est minim irure fugiat sit aute laborum.</p> </Tabs.Content> <Tabs.Content value="tab-2"> <h1 class="text-xl font-bold">Laboris aliquip</h1> <p>Veniam eu occaecat cillum ut anim esse minim anim veniam amet quis culpa excepteur aute. Nulla duis nostrud laborum Lorem esse ea consectetur sunt. Ut dolor eu sit ipsum Lorem enim non nisi incididunt in proident aliqua. Sunt cupidatat officia amet quis irure qui tempor ipsum labore deserunt occaecat enim labore id.</p> </Tabs.Content> <Tabs.Content value="tab-3"> <h1 class="text-xl font-bold">Amet nisi labore</h1> <p>Aliqua occaecat voluptate laborum amet fugiat. Duis dolore exercitation id anim sit ut ipsum occaecat consectetur ullamco dolor consequat occaecat. Aute irure nisi sint minim id ad laboris et sunt do ad pariatur consectetur. </p> </Tabs.Content></Tabs.Root>
<custom-tabs> <ul class="mb-16 flex list-none border-b p-0" role="tablist"> <li> <button id="tab-tab-1" class="group cursor-pointer bg-transparent px-8 pb-4 transition-colors aria-selected:font-bold aria-selected:shadow-[inset_0_-3px_theme(colors.blue.500)]" role="tab" aria-controls="tabpanel-tab-1" aria-selected="true" > <span class="pointer-events-none mb-4 flex items-center justify-center rounded px-8 py-0 group-hover:bg-gray-100 group-active:bg-gray-200 dark:group-hover:bg-gray-800 dark:group-active:bg-gray-700" >Tab 1</span > </button> </li> <li> <button id="tab-tab-2" class="group cursor-pointer bg-transparent px-8 pb-4 transition-colors aria-selected:font-bold aria-selected:shadow-[inset_0_-3px_theme(colors.blue.500)]" role="tab" aria-controls="tabpanel-tab-2" aria-selected="false" > <span class="pointer-events-none mb-4 flex items-center justify-center rounded px-8 py-0 group-hover:bg-gray-100 group-active:bg-gray-200 dark:group-hover:bg-gray-800 dark:group-active:bg-gray-700" >Tab 2</span > </button> </li> <li> <button id="tab-tab-3" class="group cursor-pointer bg-transparent px-8 pb-4 transition-colors aria-selected:font-bold aria-selected:shadow-[inset_0_-3px_theme(colors.blue.500)]" role="tab" aria-controls="tabpanel-tab-3" aria-selected="false" > <span class="pointer-events-none mb-4 flex items-center justify-center rounded px-8 py-0 group-hover:bg-gray-100 group-active:bg-gray-200 dark:group-hover:bg-gray-800 dark:group-active:bg-gray-700" >Tab 3</span > </button> </li> </ul> <div id="tabpanel-tab-1" aria-labelledby="tab-tab-1" role="tabpanel" tabindex="0" > <h1 class="text-xl font-bold">Consequat sint dolor incididunt</h1> <p> Ex do nulla elit amet et laboris tempor qui nulla. Sunt amet voluptate enim dolore sit cupidatat. Incididunt ex non minim aliquip sunt eu. Est elit et eu enim sunt enim qui do adipisicing. Culpa veniam laboris nostrud tempor est minim irure fugiat sit aute laborum. </p> <a href="#tab-tab-1" class="btn-text sr-only focus-visible:not-sr-only" >Back to tabs list</a > </div> <div id="tabpanel-tab-2" aria-labelledby="tab-tab-2" role="tabpanel" tabindex="0" hidden="true" > <h1 class="text-xl font-bold">Laboris aliquip</h1> <p> Veniam eu occaecat cillum ut anim esse minim anim veniam amet quis culpa excepteur aute. Nulla duis nostrud laborum Lorem esse ea consectetur sunt. Ut dolor eu sit ipsum Lorem enim non nisi incididunt in proident aliqua. Sunt cupidatat officia amet quis irure qui tempor ipsum labore deserunt occaecat enim labore id. </p> <a href="#tab-tab-2" class="btn-text sr-only focus-visible:not-sr-only" >Back to tabs list</a > </div> <div id="tabpanel-tab-3" aria-labelledby="tab-tab-3" role="tabpanel" tabindex="0" hidden="true" > <h1 class="text-xl font-bold">Amet nisi labore</h1> <p> Aliqua occaecat voluptate laborum amet fugiat. Duis dolore exercitation id anim sit ut ipsum occaecat consectetur ullamco dolor consequat occaecat. Aute irure nisi sint minim id ad laboris et sunt do ad pariatur consectetur. </p> <a href="#tab-tab-3" class="btn-text sr-only focus-visible:not-sr-only" >Back to tabs list</a > </div></custom-tabs>
Anatomy
This component is comprised of several elements that work together to create a tablist. The following are the elements that make up the Tabs component.
---import Tabs from '@/components/navigation/tabs/Tabs'---
<Tabs.Root> <Tabs.List> <Tabs.Trigger /> </Tabs.List> <Tabs.Content /></Tabs.Root>
API Reference
Root
The root element of the Tabs component. This element wraps the tablist and content elements and provides the scripting logic to toggle between tabs.
Prop | Type | Default |
---|---|---|
defaultValue | string | — |
List
The list element of the Tabs component. This element wraps the tab trigger elements and is based on the <ul>
element and can take in any generic HTML attributes.
Trigger
The trigger element of the Tabs component. This component is based on the <button>
element and can take in any additional HTML attributes. The following are the custom props that can be passed to the component.
Prop | Type | Default |
---|---|---|
value * | string | — |
Content
The content element of the Tabs component. This component is based on the <div>
element and can take in any additional HTML attributes. The following are the custom props that can be passed to the component.
Prop | Type | Default |
---|---|---|
value * | string | — |
Accessibility
The <Tabs>
component has some predefined accessibility features that are important to consider when implementing it in your project. The following are some of the key accessibility features that are included in the component:
- The component uses
aria
attributes to establish a clear association between the content and the tabs. - When a tab is activated, the corresponding content is brought into focus, enhancing the experience for users relying on keyboard navigation.
- The component includes a hidden jump link within the content field that points back to the tab trigger.
Examples
Change the Default Tab
You can change the default tab that is opened when the component is first rendered by using the defaultValue
prop on the <Tabs.Root>
component.
Consequat sint dolor incididunt
Ex do nulla elit amet et laboris tempor qui nulla. Sunt amet voluptate enim dolore sit cupidatat. Incididunt ex non minim aliquip sunt eu. Est elit et eu enim sunt enim qui do adipisicing. Culpa veniam laboris nostrud tempor est minim irure fugiat sit aute laborum.
Back to tabs listLaboris aliquip
Veniam eu occaecat cillum ut anim esse minim anim veniam amet quis culpa excepteur aute. Nulla duis nostrud laborum Lorem esse ea consectetur sunt. Ut dolor eu sit ipsum Lorem enim non nisi incididunt in proident aliqua. Sunt cupidatat officia amet quis irure qui tempor ipsum labore deserunt occaecat enim labore id.
Back to tabs listAmet nisi labore
Aliqua occaecat voluptate laborum amet fugiat. Duis dolore exercitation id anim sit ut ipsum occaecat consectetur ullamco dolor consequat occaecat. Aute irure nisi sint minim id ad laboris et sunt do ad pariatur consectetur.
Back to tabs list<Tabs.Root defaultValue="tab-2"> <Tabs.List> <Tabs.Trigger value="tab-1">Tab 1</Tabs.Trigger> <Tabs.Trigger value="tab-2">Tab 2</Tabs.Trigger> <Tabs.Trigger value="tab-3">Tab 3</Tabs.Trigger> </Tabs.List> <Tabs.Content value="tab-1"> <h1 class="text-xl font-bold">Consequat sint dolor incididunt</h1> <p>Ex do nulla elit amet et laboris tempor qui nulla. Sunt amet voluptate enim dolore sit cupidatat. Incididunt ex non minim aliquip sunt eu. Est elit et eu enim sunt enim qui do adipisicing. Culpa veniam laboris nostrud tempor est minim irure fugiat sit aute laborum.</p> </Tabs.Content> <Tabs.Content value="tab-2"> <h1 class="text-xl font-bold">Laboris aliquip</h1> <p>Veniam eu occaecat cillum ut anim esse minim anim veniam amet quis culpa excepteur aute. Nulla duis nostrud laborum Lorem esse ea consectetur sunt. Ut dolor eu sit ipsum Lorem enim non nisi incididunt in proident aliqua. Sunt cupidatat officia amet quis irure qui tempor ipsum labore deserunt occaecat enim labore id.</p> </Tabs.Content> <Tabs.Content value="tab-3"> <h1 class="text-xl font-bold">Amet nisi labore</h1> <p>Aliqua occaecat voluptate laborum amet fugiat. Duis dolore exercitation id anim sit ut ipsum occaecat consectetur ullamco dolor consequat occaecat. Aute irure nisi sint minim id ad laboris et sunt do ad pariatur consectetur. </p> </Tabs.Content></Tabs.Root>
<custom-tabs data-default-tab="tab-2"> <ul class="mb-16 flex list-none border-b p-0" role="tablist"> <li> <button id="tab-tab-1" class="group cursor-pointer bg-transparent px-8 pb-4 transition-colors aria-selected:font-bold aria-selected:shadow-[inset_0_-3px_theme(colors.blue.500)]" role="tab" aria-controls="tabpanel-tab-1" aria-selected="false" > <span class="pointer-events-none mb-4 flex items-center justify-center rounded px-8 py-0 group-hover:bg-gray-100 group-active:bg-gray-200 dark:group-hover:bg-gray-800 dark:group-active:bg-gray-700" >Tab 1</span > </button> </li> <li> <button id="tab-tab-2" class="group cursor-pointer bg-transparent px-8 pb-4 transition-colors aria-selected:font-bold aria-selected:shadow-[inset_0_-3px_theme(colors.blue.500)]" role="tab" aria-controls="tabpanel-tab-2" aria-selected="true" > <span class="pointer-events-none mb-4 flex items-center justify-center rounded px-8 py-0 group-hover:bg-gray-100 group-active:bg-gray-200 dark:group-hover:bg-gray-800 dark:group-active:bg-gray-700" >Tab 2</span > </button> </li> <li> <button id="tab-tab-3" class="group cursor-pointer bg-transparent px-8 pb-4 transition-colors aria-selected:font-bold aria-selected:shadow-[inset_0_-3px_theme(colors.blue.500)]" role="tab" aria-controls="tabpanel-tab-3" aria-selected="false" > <span class="pointer-events-none mb-4 flex items-center justify-center rounded px-8 py-0 group-hover:bg-gray-100 group-active:bg-gray-200 dark:group-hover:bg-gray-800 dark:group-active:bg-gray-700" >Tab 3</span > </button> </li> </ul> <div id="tabpanel-tab-1" aria-labelledby="tab-tab-1" role="tabpanel" tabindex="0" hidden="true" > <h1 class="text-xl font-bold">Consequat sint dolor incididunt</h1> <p> Ex do nulla elit amet et laboris tempor qui nulla. Sunt amet voluptate enim dolore sit cupidatat. Incididunt ex non minim aliquip sunt eu. Est elit et eu enim sunt enim qui do adipisicing. Culpa veniam laboris nostrud tempor est minim irure fugiat sit aute laborum. </p> <a href="#tab-tab-1" class="btn-text sr-only focus-visible:not-sr-only" >Back to tabs list</a > </div> <div id="tabpanel-tab-2" aria-labelledby="tab-tab-2" role="tabpanel" tabindex="0" > <h1 class="text-xl font-bold">Laboris aliquip</h1> <p> Veniam eu occaecat cillum ut anim esse minim anim veniam amet quis culpa excepteur aute. Nulla duis nostrud laborum Lorem esse ea consectetur sunt. Ut dolor eu sit ipsum Lorem enim non nisi incididunt in proident aliqua. Sunt cupidatat officia amet quis irure qui tempor ipsum labore deserunt occaecat enim labore id. </p> <a href="#tab-tab-2" class="btn-text sr-only focus-visible:not-sr-only" >Back to tabs list</a > </div> <div id="tabpanel-tab-3" aria-labelledby="tab-tab-3" role="tabpanel" tabindex="0" hidden="true" > <h1 class="text-xl font-bold">Amet nisi labore</h1> <p> Aliqua occaecat voluptate laborum amet fugiat. Duis dolore exercitation id anim sit ut ipsum occaecat consectetur ullamco dolor consequat occaecat. Aute irure nisi sint minim id ad laboris et sunt do ad pariatur consectetur. </p> <a href="#tab-tab-3" class="btn-text sr-only focus-visible:not-sr-only" >Back to tabs list</a > </div></custom-tabs>
Astro Component
It’s recommended that an element like this is made as a reusable component. In this case, we have put together the following Astro compound component to serve as a blueprint for how you could possibly set up a Tabs component, and what properties and accessibility to consider.
import Root from './Root.astro'import List from './List.astro'import Trigger from './Trigger.astro'import Content from './Content.astro'
export default Object.assign({ Root, List, Trigger, Content })
---interface Props { /** The default tab to be selected */ defaultValue?: string}
const { defaultValue } = Astro.props---
<custom-tabs data-default-tab={defaultValue} data-component="Tabs"> <slot /></custom-tabs>
<script> class CustomDialog extends HTMLElement { constructor() { super()
this.resetTabs() this.setDefaultTab() }
connectedCallback() { this.tabs?.forEach((tab: HTMLElement) => { tab.addEventListener('click', this.handleTabClick) }) }
disconnectedCallback() { this.tabs?.forEach((tab: HTMLElement) => { tab.removeEventListener('click', this.handleTabClick) }) }
private get tabs(): NodeListOf<HTMLElement> { return this.querySelectorAll('[role="tab"]') }
private get tabpanels(): NodeListOf<HTMLElement> { return this.querySelectorAll('[role="tabpanel"]') }
private get defaultTab(): HTMLElement { return this.querySelector(`#tab-${this.dataset.defaultTab}`) }
private get defaultTabPanel(): HTMLElement { return this.querySelector(`#tabpanel-${this.dataset.defaultTab}`) }
private setDefaultTab = () => { const tab = this.defaultTab || this.tabs[0] const panel = this.defaultTabPanel || this.tabpanels[0]
tab.setAttribute('aria-selected', 'true') panel.removeAttribute('hidden') }
private resetTabs = () => { this.tabs.forEach((tab: HTMLElement) => { tab.setAttribute('aria-selected', 'false') }) this.tabpanels.forEach((tabpanel: HTMLElement) => { tabpanel.setAttribute('hidden', 'true') }) }
private handleTabClick = (event: Event) => { this.resetTabs()
const targetElement = event.target as HTMLElement const targetElementPanel = this.querySelector( `#${targetElement.getAttribute('aria-controls')}`, ) as HTMLElement
targetElement.setAttribute('aria-selected', 'true') targetElementPanel?.removeAttribute('hidden') targetElementPanel?.focus() } }
customElements.define('custom-tabs', CustomDialog)</script>
---import type { HTMLAttributes } from 'astro/types'
interface Props extends HTMLAttributes<'ul'> {}
const { class: className, ...attrs } = Astro.props---
<ul class:list={[ { 'mb-16 flex list-none overflow-auto border-b p-0': !className }, className, ]} role="tablist" {...attrs}> <slot /></ul>
---import type { HTMLAttributes } from 'astro/types'
interface Props extends HTMLAttributes<'button'> { /** The controlled value of the tab to bind to the correct content panel.
Should be unique within the set of tabs and in kebab-case. */ value: string}
const { value, class: className, ...attrs } = Astro.props---
<li> <button id=`tab-${value}` class:list={[ 'aria-selected:font-bold', { 'group shrink-0 cursor-pointer whitespace-nowrap bg-transparent px-8 pb-4 transition-colors aria-selected:shadow-[inset_0_-3px_theme(colors.blue.500)]': !className, }, className, ]} {...attrs} role="tab" aria-controls={`tabpanel-${value}`} ><span class="pointer-events-none mb-4 flex items-center justify-center rounded px-8 py-0 group-hover:bg-gray-100 group-active:bg-gray-200 dark:group-hover:bg-gray-800 dark:group-active:bg-gray-700" ><slot /></span ></button ></li>
---import type { HTMLAttributes } from 'astro/types'
interface Props extends HTMLAttributes<'div'> { /** The controlled value of the tab to bind to the correct content panel. */ value: string}
const { value, ...attrs } = Astro.props---
<div id={`tabpanel-${value}`} aria-labelledby={`tab-${value}`} role="tabpanel" tabindex="0" hidden="true" {...attrs}> <slot /> <a href={`#tab-${value}`} class="btn-text sr-only focus-visible:not-sr-only" >Back to tabs list</a ></div>