QUI React

Tabs

Tabs organize content into multiple, accessible sections.

import {QTabs, QTabList, QTab, QTabPanels, QTabPanel} from "@qui/react"

Overview

  • Tabs: Provides context and state for all components.
  • TabList: Wrapper for the Tab components.
  • Tab: element that serves as a label for one of the tab panels and can be activated to display that panel.
  • TabPanels: Wrapper for the TabPanel components.
  • TabPanel: element that contains the content associated with a tab.

Usage Guidelines:

  • The number of tabs should be limited to a number that is easy for the user to remember and navigate. Too many tabs can be overwhelming for the user, while too few tabs may not provide enough flexibility.
  • Tab labels should be concise, and they should accurately reflect the content of the corresponding section.
  • Tabs should be ordered in a way that makes sense for the user.

Accessibility

Our implementation is based on the ARIA Tabs pattern.

Examples

Showcase

Profile Panel
import {ReactNode} from "react"
import {Files, Settings, Settings2, User} from "lucide-react"
import {QTab, QTabList, QTabPanel, QTabPanels, QTabs} from "@qui/react"
export default function Showcase(): ReactNode {
return (
<QTabs>
<QTabList>
<QTab startIcon={User}>Profile</QTab>
<QTab startIcon={Settings2}>Preferences</QTab>
<QTab disabled startIcon={Settings}>
Disabled
</QTab>
<QTab startIcon={Files}>Files</QTab>
</QTabList>
<QTabPanels>
<QTabPanel>Profile Panel</QTabPanel>
<QTabPanel>Preferences Panel</QTabPanel>
<QTabPanel>Disabled Panel</QTabPanel>
<QTabPanel>Files Panel</QTabPanel>
</QTabPanels>
</QTabs>
)
}

Manual Activation

It is recommended that tabs activate automatically when they receive focus as long as their associated tab panels are displayed without noticeable latency. This is the default behavior. Set manual to true to disable this functionality. When true, tabs will not automatically open when focused, and will require either a click or key event (Space or Enter).

Profile Panel
import {ReactNode} from "react"
import {Files, Settings, Settings2, User} from "lucide-react"
import {QTab, QTabList, QTabPanel, QTabPanels, QTabs} from "@qui/react"
export default function ManualActivation(): ReactNode {
return (
<QTabs manual>
<QTabList>
<QTab startIcon={User}>Profile</QTab>
<QTab startIcon={Settings2}>Preferences</QTab>
<QTab disabled startIcon={Settings}>
Disabled
</QTab>
<QTab startIcon={Files}>Files</QTab>
</QTabList>
<QTabPanels>
<QTabPanel>Profile Panel</QTabPanel>
<QTabPanel>Preferences Panel</QTabPanel>
<QTabPanel>Disabled Panel</QTabPanel>
<QTabPanel>Files Panel</QTabPanel>
</QTabPanels>
</QTabs>
)
}

Vertical Tabs

Set the orientation prop to vertical to orient the tabs from top to bottom instead of left to right. In vertical mode, the active tab panel displays to the right or left of the tablist, depending on the value of alignment.

Profile Panel
Profile Panel
import {ReactNode} from "react"
import {Files, Settings2, User} from "lucide-react"
import {QTab, QTabList, QTabPanel, QTabPanels, QTabs} from "@qui/react"
export default function Showcase(): ReactNode {
const tabContent = (
<>
<QTabList>
<QTab startIcon={User}>Profile</QTab>
<QTab startIcon={Settings2}>Preferences</QTab>
<QTab startIcon={Files}>Files</QTab>
</QTabList>
<QTabPanels>
<QTabPanel>Profile Panel</QTabPanel>
<QTabPanel>Preferences Panel</QTabPanel>
<QTabPanel>Files Panel</QTabPanel>
</QTabPanels>
</>
)
return (
<div className="flex w-80 flex-col gap-8">
<div className="q-border-default flex w-full rounded border p-4">
<QTabs fullWidth orientation="vertical">
{tabContent}
</QTabs>
</div>
<div className="q-border-default flex w-full rounded border p-4">
<QTabs alignment="end" fullWidth orientation="vertical">
{tabContent}
</QTabs>
</div>
</div>
)
}

Controlled

The onChange callback returns the active tab's index whenever the user changes tabs. If you intend to control the tabs programmatically, use this with the index prop.

import {ReactNode, useState} from "react"
import {ChevronLeft, ChevronRight} from "lucide-react"
import {QButton, QTab, QTabList, QTabPanel, QTabPanels, QTabs} from "@qui/react"
export default function Controlled(): ReactNode {
const [index, setIndex] = useState(0)
const next = () => setIndex((prevIndex) => Math.min(2, prevIndex + 1))
const prev = () => setIndex((prevIndex) => Math.max(0, prevIndex - 1))
return (
<QTabs accent="secondary" index={index} onChange={setIndex}>
<QTabList>
<QTab>One</QTab>
<QTab>Two</QTab>
<QTab>Three</QTab>
</QTabList>
<QTabPanels>
<QTabPanel className="flex gap-2">
<QButton disabled endIcon={ChevronLeft} variant="outline">
Prev
</QButton>
<QButton endIcon={ChevronRight} onClick={next} variant="outline">
Next
</QButton>
</QTabPanel>
<QTabPanel className="flex gap-2">
<QButton endIcon={ChevronLeft} onClick={prev} variant="outline">
Prev
</QButton>
<QButton endIcon={ChevronRight} onClick={next} variant="outline">
Next
</QButton>
</QTabPanel>
<QTabPanel className="flex gap-2">
<QButton endIcon={ChevronLeft} onClick={prev} variant="outline">
Prev
</QButton>
<QButton disabled endIcon={ChevronRight} variant="outline">
Next
</QButton>
</QTabPanel>
</QTabPanels>
</QTabs>
)
}

Dismissable Tabs

Use the dismissable prop to render a close icon button. When clicked, the onTabDismissed callback will be called with the index and props of the dismissed tab, as well as the event that triggered the dismissal. Use this information to re-compute the visible tabs:

Profile Panel
import {ReactNode, useState} from "react"
import {Files, Settings, Settings2, User} from "lucide-react"
import {
QTab,
QTabDismissEvent,
QTabList,
QTabPanel,
QTabPanelProps,
QTabPanels,
QTabProps,
QTabs,
} from "@qui/react"
interface TabAndPanel {
id: string
panel: QTabPanelProps
tab: QTabProps
}
export default function Dismissable(): ReactNode {
const [tabConfig, setTabConfig] = useState<TabAndPanel[]>([
{
id: "profile",
panel: {
children: "Profile Panel",
},
tab: {
children: "Profile",
startIcon: User,
},
},
{
id: "preferences",
panel: {
children: "Preferences Panel",
},
tab: {
children: "Preferences",
startIcon: Settings2,
},
},
{
id: "disabled",
panel: {
children: "Disabled Panel",
},
tab: {
children: "Disabled",
disabled: true,
startIcon: Settings,
},
},
{
id: "files",
panel: {
children: "Files Panel",
},
tab: {
children: "Files",
startIcon: Files,
},
},
])
const handleDismiss: QTabDismissEvent = (index) => {
setTabConfig((prevState) => [
...prevState.slice(0, index),
...prevState.slice(index + 1),
])
}
return (
<QTabs dismissable onTabDismissed={handleDismiss}>
<QTabList>
{tabConfig.map((entry) => {
return (
<QTab key={entry.id} {...entry.tab}>
{entry.tab.children}
</QTab>
)
})}
</QTabList>
<QTabPanels>
{tabConfig.map((entry) => {
return (
<QTabPanel key={entry.id} {...entry.panel}>
{entry.panel.children}
</QTabPanel>
)
})}
</QTabPanels>
</QTabs>
)
}

Playground

Profile Panel
Orientation
Alignment
Accent
Size

API

Props

Name & DescriptionOption(s)Default
React children prop.
ReactNode
The style of the tab buttons.
| 'primary'
| 'secondary'
'primary'
Governs the horizontal placement of the tablist relative to the panel content.
| 'start'
| 'end'
'start'
When true, the tab indicator bar will animate into position whenever a tab is selected.
boolean
true
The component used for the root node. It can be a React component or element.
| ElementType
| ComponentType
'div'
The className property of the Element interface gets and sets the value of the class attribute of the specified element.
string
Controlled closed tabs. Each entry maps to a tab's numeric index. Closed tabs are not visible.
Related symbols:
number[]
The default closed tabs. Each entry maps to a tab's numeric index.
number[]
[]
The initial index of the selected tab.
number
Render a close icon that calls onTabDismissed on click.
boolean
false
If true, the tab container will take up the full height of its parent container
boolean
false
If true, the tab container will take up the full width of its parent container
boolean
false
The index of the selected tab (in controlled mode)
number
The lazy behavior of tab panel content when not active.
  • keepMounted: The contents of inactive tab panels are initially unmounted, but stay mounted after selection.
  • unmount: The contents of inactive tab panels are always unmounted.
  • off: The contents of inactive panels are always mounted.
| 'unmount'
| 'keepMounted'
| 'off'
'off'
If true, each tab will be manually activated and display its panel by pressing Space or Enter.

If
false, each tab will be automatically activated and their panel is displayed when they receive focus.
boolean
false
Callback fired when the index (controlled or un-controlled) changes.

The callback includes a
reason parameter which indicates why the event was fired:
    • select: The user selected a new tab.
    • reset: The available tabs were added or removed, and the previously selected tab index is no longer available.
    (
    index: number,
    reason: 'select' | 'reset',
    event?: SyntheticEvent,
    ) => void
    Callback fired when a tab's close icon is clicked.
      (
      index: number,
      props: QTabProps,
      event: SyntheticEvent,
      ) => void
      The orientation of the tab list.
      • horizontal: tabs are displayed from left-to-right.
      • vertical: tabs are displayed from top-to-bottom.
      | 'horizontal'
      | 'vertical'
      'horizontal'
      
      The size of the tab buttons and their icons.
      | 's'
      | 'm'
      | 'l'
      'm'
      
      The style global attribute contains CSS styling declarations to be applied to the element.
      CSSProperties
      Description
      React children prop.
      Type
      | 'primary'
      | 'secondary'
      Default
      'primary'
      
      Description
      The style of the tab buttons.
      Type
      | 'start'
      | 'end'
      Default
      'start'
      
      Description
      Governs the horizontal placement of the tablist relative to the panel content.
      Type
      boolean
      Default
      true
      
      Description
      When true, the tab indicator bar will animate into position whenever a tab is selected.
      Type
      | ElementType
      | ComponentType
      Default
      'div'
      
      Description
      The component used for the root node. It can be a React component or element.
      Type
      string
      Description
      The className property of the Element interface gets and sets the value of the class attribute of the specified element.
      Type
      number[]
      Description
      Controlled closed tabs. Each entry maps to a tab's numeric index. Closed tabs are not visible.
      Related symbols
      Related symbols:
      Type
      number[]
      Default
      []
      
      Description
      The default closed tabs. Each entry maps to a tab's numeric index.
      Type
      number
      Description
      The initial index of the selected tab.
      Type
      boolean
      Default
      false
      
      Description
      Render a close icon that calls onTabDismissed on click.
      Type
      boolean
      Default
      false
      
      Description
      If true, the tab container will take up the full height of its parent container
      Type
      boolean
      Default
      false
      
      Description
      If true, the tab container will take up the full width of its parent container
      Type
      number
      Description
      The index of the selected tab (in controlled mode)
      Type
      | 'unmount'
      | 'keepMounted'
      | 'off'
      Default
      'off'
      
      Description
      The lazy behavior of tab panel content when not active.
      • keepMounted: The contents of inactive tab panels are initially unmounted, but stay mounted after selection.
      • unmount: The contents of inactive tab panels are always unmounted.
      • off: The contents of inactive panels are always mounted.
      Type
      boolean
      Default
      false
      
      Description
      If true, each tab will be manually activated and display its panel by pressing Space or Enter.

      If
      false, each tab will be automatically activated and their panel is displayed when they receive focus.
      Type
      (
      index: number,
      reason: 'select' | 'reset',
      event?: SyntheticEvent,
      ) => void
      Description
      Callback fired when the index (controlled or un-controlled) changes.

      The callback includes a
      reason parameter which indicates why the event was fired:
        • select: The user selected a new tab.
        • reset: The available tabs were added or removed, and the previously selected tab index is no longer available.
        Type
        (
        index: number,
        props: QTabProps,
        event: SyntheticEvent,
        ) => void
        Description
        Callback fired when a tab's close icon is clicked.
          Type
          | 'horizontal'
          | 'vertical'
          Default
          'horizontal'
          
          Description
          The orientation of the tab list.
          • horizontal: tabs are displayed from left-to-right.
          • vertical: tabs are displayed from top-to-bottom.
          Type
          | 's'
          | 'm'
          | 'l'
          Default
          'm'
          
          Description
          The size of the tab buttons and their icons.
          Description
          The style global attribute contains CSS styling declarations to be applied to the element.

          Keyboard Events

          • When focus moves into the tab list, places focus on the active tab element.

          • When the tab list contains the focus, moves focus to the next element in the page tab sequence outside the tablist, which is the tabpanel.

          If the tablist is focused and orientation is horizontal:

          • moves focus to the previous tab. If focus is on the first tab, moves focus to the last tab. If manual is false, activates the newly focused tab. Also see Manual Activation.

          • moves focus to the previous tab. If focus is on the first tab, moves focus to the last tab. If manual is false, activates the newly focused tab. Also see Manual Activation.

          If the tablist is focused and orientation is vertical:

          When focus is on a tab in the tablist with either horizontal or vertical orientation:

          • Activates the tab if it was not activated automatically on focus.

          Moves focus to the first available tab. If manual is false, activates the newly focused tab. Also see Manual Activation.

          Moves focus to the last available tab. If manual is false, activates the newly focused tab. Also see Manual Activation.

          • If dismissable is true, deletes (closes) the current tab element and its associated tab panel, sets focus on the tab following the tab that was closed, and optionally activates the newly focused tab.

            • If there is not a tab that followed the tab that was deleted, e.g., the deleted tab was the right-most tab in a left-to-right horizontal tab list, sets focus on and optionally activates the tab that preceded the deleted tab.

            • If the application allows all tabs to be deleted, and the user deletes the last remaining tab in the tab list, the application moves focus to another element that provides a logical work flow.