Hanzo GUI

Tabs

Use in pages to manage sub-pages

StyledUnstyledHeadless

`

`

`

  • Accessible, easy to compose, customize and animate
  • Sizable & works controlled or uncontrolled
  • Supports automatic and manual activation modes
  • Full keyboard navigation

Installation

Tabs is already installed in @hanzo/gui, or you can install it independently:

npm install @hanzogui/tabs

Tabs is already installed in @hanzo/gui, or you can install it independently:

npm install @hanzogui/tabs

To use the headless tabs, import from the @hanzogui/tabs-headless package. This package has no dependency on @hanzogui/core and provides hooks for building custom tab interfaces with any styling solution.

npm install @hanzogui/tabs-headless

Usage

import { SizableText, Tabs } from '@hanzo/gui'

export default () => (
  <Tabs defaultValue="tab1" width={400}>
    <Tabs.List>
      <Tabs.Tab value="tab1">
        <SizableText>Tab 1</SizableText>
      </Tabs.Tab>
      <Tabs.Tab value="tab2">
        <SizableText>Tab 2</SizableText>
      </Tabs.Tab>
    </Tabs.List>

    <Tabs.Content value="tab1">
      <H5>Tab 1</H5>
    </Tabs.Content>
    <Tabs.Content value="tab2">
      <H5>Tab 2</H5>
    </Tabs.Content>
  </Tabs>
)

Use the createTabs export to create fully custom tabs that still use the Hanzo GUI styling system. You provide your own styled frame components and get back a fully functional tabs component.

The useTabs hook provides all the state and accessibility props needed to build custom tabs with any styling solution.

import { useTabs } from '@hanzogui/tabs-headless'

function MyTabs() {
  const { tabsProps, listProps, getTabProps, getContentProps, value } = useTabs({
    defaultValue: 'tab1',
    orientation: 'horizontal',
  })

  return (
    <div {...tabsProps}>
      <div {...listProps}>
        <button {...getTabProps('tab1')}>Tab 1</button>
        <button {...getTabProps('tab2')}>Tab 2</button>
      </div>

      <div {...getContentProps('tab1')}>{value === 'tab1' && <p>Content 1</p>}</div>
      <div {...getContentProps('tab2')}>{value === 'tab2' && <p>Content 2</p>}</div>
    </div>
  )
}

Component-Based API

For more complex use cases, you can use the context-based hooks:

import { useTabs, TabsProvider, useTab, useTabContent } from '@hanzogui/tabs-headless'

function Tabs({ children, ...props }) {
  const { contextValue, tabsProps } = useTabs(props)
  return (
    <TabsProvider value={contextValue}>
      <div {...tabsProps}>{children}</div>
    </TabsProvider>
  )
}

function Tab({ value, children }) {
  const { isSelected, tabProps } = useTab({ value })
  return (
    <button {...tabProps} style={{ fontWeight: isSelected ? 'bold' : 'normal' }}>
      {children}
    </button>
  )
}

function TabContent({ value, children }) {
  const { shouldMount, contentProps } = useTabContent({ value })
  if (!shouldMount) return null
  return <div {...contentProps}>{children}</div>
}

API Reference

Tabs

Root tabs component. Extends Stack. Passing the size prop to this component will affect the descendants.

When using createTabs, you provide three styled components:

  • TabsFrame: The root container component
  • TabFrame: The tab trigger component
  • ContentFrame: The content container component

The useTabs hook accepts these options and returns props/helpers:

PropTypeDefaultRequired
valuestring--
defaultValuestring--
onValueChange(value: string) => void--
orientation"horizontal" | "vertical"horizontal-
dir"ltr" | "rtl"--
activationMode"manual" | "automatic"manual-
loopbooleantrue-

useTabs Return Value

PropertyTypeDescription
valuestringThe currently selected tab value
setValue(value: string) => voidFunction to change the selected tab
direction'ltr' | 'rtl'The resolved text direction
tabsPropsobjectProps to spread on the tabs container
listPropsobjectProps to spread on the tab list
getTabProps(value: string, disabled?: boolean) => objectGet props for a tab trigger
getContentProps(value: string) => objectGet props for a tab content panel
contextValueTabsContextValueContext value for component-based API

useTab

Hook for individual tab triggers when using the component-based API.

const { isSelected, tabProps } = useTab({
  value: 'tab1',
  disabled: false,
  onPress: () => {},
  onKeyDown: () => {},
  onFocus: () => {},
})

useTabContent

Hook for tab content panels when using the component-based API.

const { isSelected, shouldMount, contentProps } = useTabContent({
  value: 'tab1',
  forceMount: false,
})

Tabs.List

Container for the trigger buttons. Supports scrolling by extending Group. You can disable passing border radius to children by passing disablePassBorderRadius.

PropTypeDefaultRequired
loopbooleantrue-

Since Tabs.List extends Group, the same limitation applies: automatic border radius detection only works when Tabs.Tab is a direct child of Tabs.List. If you wrap tabs in custom components, see the Group docs on nested items for workarounds.

Tabs.Tab

Extends ThemeableStack, adding:

PropTypeDefaultRequired
valuestring--
onInteraction(type: InteractionType, layout: TabLayout | null) => void--
disabledboolean--
unstyledboolean--
activeStyleStyleProp--
activeThemestring | null--

Tabs.Content

Where each tab's content will be shown. Extends ThemeableStack, adding:

PropTypeDefaultRequired
valuestring--
forceMountbooleanfalse-

Examples

Animations

Here is a demo with more advanced animations using AnimatePresence and Tab's onInteraction prop.

Last updated on

On this page