Skip to main content

Tabs organize content into multiple sections and allow users to navigate between them

Import

import { Tabs } from "heroui-solid";

Usage

View your project overview and recent activity.

import { Tabs } from "heroui-solid"

export function Basic() {
  return (
    <Tabs class="w-full max-w-md">
      <Tabs.ListContainer>
        <Tabs.List aria-label="Options">
          <Tabs.Tab id="overview">
            Overview
            <Tabs.Indicator />
          </Tabs.Tab>
          <Tabs.Tab id="analytics">
            Analytics
            <Tabs.Indicator />
          </Tabs.Tab>
          <Tabs.Tab id="reports">
            Reports
            <Tabs.Indicator />
          </Tabs.Tab>
        </Tabs.List>
      </Tabs.ListContainer>
      <Tabs.Panel class="pt-4" id="overview">
        <p>View your project overview and recent activity.</p>
      </Tabs.Panel>
      <Tabs.Panel class="pt-4" id="analytics">
        <p>Track your metrics and analyze performance data.</p>
      </Tabs.Panel>
      <Tabs.Panel class="pt-4" id="reports">
        <p>Generate and download detailed reports.</p>
      </Tabs.Panel>
    </Tabs>
  )
}

Anatomy

Import the Tabs component and access all parts using dot notation.

import { Tabs } from "heroui-solid";

export default () => (
  <Tabs>
    <Tabs.ListContainer>
      <Tabs.List aria-label="Options">
        <Tabs.Tab>
          <Tabs.Separator /> {/* Optional */}
          <Tabs.Indicator />
        </Tabs.Tab>
      </Tabs.List>
    </Tabs.ListContainer>
    <Tabs.Panel />
  </Tabs>
);

Vertical

Account Settings

Manage your account information and preferences.

import { Tabs } from "heroui-solid"

export function Vertical() {
  return (
    <Tabs class="w-full max-w-lg" orientation="vertical">
      <Tabs.ListContainer>
        <Tabs.List aria-label="Vertical tabs">
          <Tabs.Tab id="account">
            Account
            <Tabs.Indicator />
          </Tabs.Tab>
          <Tabs.Tab id="security">
            Security
            <Tabs.Indicator />
          </Tabs.Tab>
          <Tabs.Tab id="notifications">
            Notifications
            <Tabs.Indicator />
          </Tabs.Tab>
          <Tabs.Tab id="billing">
            Billing
            <Tabs.Indicator />
          </Tabs.Tab>
        </Tabs.List>
      </Tabs.ListContainer>
      <Tabs.Panel class="px-4" id="account">
        <h3 class="mb-2 font-semibold">Account Settings</h3>
        <p class="text-sm text-muted">
          Manage your account information and preferences.
        </p>
      </Tabs.Panel>
      <Tabs.Panel class="px-4" id="security">
        <h3 class="mb-2 font-semibold">Security Settings</h3>
        <p class="text-sm text-muted">
          Configure two-factor authentication and password settings.
        </p>
      </Tabs.Panel>
      <Tabs.Panel class="px-4" id="notifications">
        <h3 class="mb-2 font-semibold">Notification Preferences</h3>
        <p class="text-sm text-muted">
          Choose how and when you want to receive notifications.
        </p>
      </Tabs.Panel>
      <Tabs.Panel class="px-4" id="billing">
        <h3 class="mb-2 font-semibold">Billing Information</h3>
        <p class="text-sm text-muted">
          View and manage your subscription and payment methods.
        </p>
      </Tabs.Panel>
    </Tabs>
  )
}

Overflow

When the tab list exceeds the available space, Tabs.ListContainer automatically renders scroll chevrons and fading edges so users can navigate the hidden tabs. This works the same way for both horizontal and vertical orientations. When the list is known to overflow, pass initialShadow to Tabs.ListContainer so the chevron and fade are already in the server-rendered HTML instead of appearing after hydration.

Overview panel content.

import { Tabs } from "heroui-solid"
import { For } from "solid-js"

const items = [
  { id: "overview", label: "Overview" },
  { id: "analytics", label: "Analytics" },
  { id: "reports", label: "Reports" },
  { id: "performance", label: "Performance" },
  { id: "engagement", label: "Engagement" },
  { id: "audience", label: "Audience" },
  { id: "acquisition", label: "Acquisition" },
  { id: "retention", label: "Retention" },
  { id: "settings", label: "Settings" }
]

export function Overflow() {
  return (
    <div class="w-[400px]">
      <Tabs>
        <Tabs.ListContainer initialShadow>
          <Tabs.List aria-label="Overflow options">
            <For each={items}>
              {(item) => (
                <Tabs.Tab id={item.id}>
                  {item.label}
                  <Tabs.Indicator />
                </Tabs.Tab>
              )}
            </For>
          </Tabs.List>
        </Tabs.ListContainer>
        <For each={items}>
          {(item) => (
            <Tabs.Panel class="pt-4" id={item.id}>
              <p>{item.label} panel content.</p>
            </Tabs.Panel>
          )}
        </For>
      </Tabs>
    </div>
  )
}

Disabled Tab

This tab is active and can be selected.

import { Tabs } from "heroui-solid"

export function Disabled() {
  return (
    <Tabs class="w-full max-w-md">
      <Tabs.ListContainer>
        <Tabs.List aria-label="Tabs with disabled">
          <Tabs.Tab id="active">
            Active
            <Tabs.Indicator />
          </Tabs.Tab>
          <Tabs.Tab isDisabled id="disabled">
            Disabled
            <Tabs.Indicator />
          </Tabs.Tab>
          <Tabs.Tab id="available">
            Available
            <Tabs.Indicator />
          </Tabs.Tab>
        </Tabs.List>
      </Tabs.ListContainer>
      <Tabs.Panel class="pt-4" id="active">
        <p>This tab is active and can be selected.</p>
      </Tabs.Panel>
      <Tabs.Panel class="pt-4" id="disabled">
        <p>This content cannot be accessed.</p>
      </Tabs.Panel>
      <Tabs.Panel class="pt-4" id="available">
        <p>This tab is also available for selection.</p>
      </Tabs.Panel>
    </Tabs>
  )
}

With Separator

Add <Tabs.Separator /> inside each <Tabs.Tab> (except the first) to display separator lines between tabs.

View your project overview and recent activity.

import { Tabs } from "heroui-solid"

export function WithSeparator() {
  return (
    <Tabs class="w-full max-w-md">
      <Tabs.ListContainer>
        <Tabs.List aria-label="Options">
          <Tabs.Tab id="overview">
            Overview
            <Tabs.Indicator />
          </Tabs.Tab>
          <Tabs.Tab id="analytics">
            <Tabs.Separator />
            Analytics
            <Tabs.Indicator />
          </Tabs.Tab>
          <Tabs.Tab id="reports">
            <Tabs.Separator />
            Reports
            <Tabs.Indicator />
          </Tabs.Tab>
        </Tabs.List>
      </Tabs.ListContainer>
      <Tabs.Panel class="pt-4" id="overview">
        <p>View your project overview and recent activity.</p>
      </Tabs.Panel>
      <Tabs.Panel class="pt-4" id="analytics">
        <p>Track your metrics and analyze performance data.</p>
      </Tabs.Panel>
      <Tabs.Panel class="pt-4" id="reports">
        <p>Generate and download detailed reports.</p>
      </Tabs.Panel>
    </Tabs>
  )
}

Custom Styles

import { Tabs } from "heroui-solid"

export function CustomStyles() {
  return (
    <Tabs class="w-full max-w-lg text-center">
      <Tabs.ListContainer>
        <Tabs.List
          aria-label="Options"
          class="w-fit *:h-6 *:w-fit *:px-3 *:text-sm *:font-normal *:data-[selected=true]:text-accent-foreground"
        >
          <Tabs.Tab id="daily">
            Daily
            <Tabs.Indicator class="bg-accent" />
          </Tabs.Tab>
          <Tabs.Tab id="weekly">
            Weekly
            <Tabs.Indicator class="bg-accent" />
          </Tabs.Tab>
          <Tabs.Tab id="bi-weekly">
            Bi-Weekly
            <Tabs.Indicator class="bg-accent" />
          </Tabs.Tab>
          <Tabs.Tab id="monthly">
            Monthly
            <Tabs.Indicator class="bg-accent" />
          </Tabs.Tab>
        </Tabs.List>
      </Tabs.ListContainer>
    </Tabs>
  )
}

Secondary Variant

View your project overview and recent activity.

import { Tabs } from "heroui-solid"

export function Secondary() {
  return (
    <Tabs class="w-full max-w-md" variant="secondary">
      <Tabs.ListContainer>
        <Tabs.List aria-label="Options">
          <Tabs.Tab id="overview">
            Overview
            <Tabs.Indicator />
          </Tabs.Tab>
          <Tabs.Tab id="analytics">
            Analytics
            <Tabs.Indicator />
          </Tabs.Tab>
          <Tabs.Tab id="reports">
            Reports
            <Tabs.Indicator />
          </Tabs.Tab>
        </Tabs.List>
      </Tabs.ListContainer>
      <Tabs.Panel class="pt-4" id="overview">
        <p>View your project overview and recent activity.</p>
      </Tabs.Panel>
      <Tabs.Panel class="pt-4" id="analytics">
        <p>Track your metrics and analyze performance data.</p>
      </Tabs.Panel>
      <Tabs.Panel class="pt-4" id="reports">
        <p>Generate and download detailed reports.</p>
      </Tabs.Panel>
    </Tabs>
  )
}

Secondary Variant Vertical

Account Settings

Manage your account information and preferences.

import { Tabs } from "heroui-solid"

export function SecondaryVertical() {
  return (
    <Tabs class="w-full max-w-lg" orientation="vertical" variant="secondary">
      <Tabs.ListContainer>
        <Tabs.List aria-label="Vertical tabs">
          <Tabs.Tab id="account">
            Account
            <Tabs.Indicator />
          </Tabs.Tab>
          <Tabs.Tab id="security">
            Security
            <Tabs.Indicator />
          </Tabs.Tab>
          <Tabs.Tab id="notifications">
            Notifications
            <Tabs.Indicator />
          </Tabs.Tab>
          <Tabs.Tab id="billing">
            Billing
            <Tabs.Indicator />
          </Tabs.Tab>
        </Tabs.List>
      </Tabs.ListContainer>
      <Tabs.Panel class="px-4" id="account">
        <h3 class="mb-2 font-semibold">Account Settings</h3>
        <p class="text-sm text-muted">
          Manage your account information and preferences.
        </p>
      </Tabs.Panel>
      <Tabs.Panel class="px-4" id="security">
        <h3 class="mb-2 font-semibold">Security Settings</h3>
        <p class="text-sm text-muted">
          Configure two-factor authentication and password settings.
        </p>
      </Tabs.Panel>
      <Tabs.Panel class="px-4" id="notifications">
        <h3 class="mb-2 font-semibold">Notification Preferences</h3>
        <p class="text-sm text-muted">
          Choose how and when you want to receive notifications.
        </p>
      </Tabs.Panel>
      <Tabs.Panel class="px-4" id="billing">
        <h3 class="mb-2 font-semibold">Billing Information</h3>
        <p class="text-sm text-muted">
          View and manage your subscription and payment methods.
        </p>
      </Tabs.Panel>
    </Tabs>
  )
}

Custom Element

HeroUI React overrides the rendered elements with a render prop. This port uses Kobalte's polymorphic as prop instead — for example, rendering each tab as an anchor:

Each tab renders as an anchor via Kobalte's polymorphic as prop.

import { Tabs } from "heroui-solid"

export function CustomElement() {
  return (
    <Tabs class="w-full max-w-md">
      <Tabs.ListContainer>
        <Tabs.List aria-label="Options">
          <Tabs.Tab as="a" href="#getting-started" id="getting-started">
            Getting Started
            <Tabs.Indicator />
          </Tabs.Tab>
          <Tabs.Tab as="a" href="#components" id="components">
            Components
            <Tabs.Indicator />
          </Tabs.Tab>
          <Tabs.Tab as="a" href="#releases" id="releases">
            Releases
            <Tabs.Indicator />
          </Tabs.Tab>
        </Tabs.List>
      </Tabs.ListContainer>
      <Tabs.Panel class="pt-4" id="getting-started">
        <p>Each tab renders as an anchor via Kobalte's polymorphic as prop.</p>
      </Tabs.Panel>
      <Tabs.Panel class="pt-4" id="components">
        <p>Track your metrics and analyze performance data.</p>
      </Tabs.Panel>
      <Tabs.Panel class="pt-4" id="releases">
        <p>Generate and download detailed reports.</p>
      </Tabs.Panel>
    </Tabs>
  )
}

Styling

Passing Tailwind CSS classes

import { Tabs } from "heroui-solid";

function CustomTabs() {
  return (
    <Tabs class="w-full max-w-lg text-center">
      <Tabs.ListContainer>
        <Tabs.List
          aria-label="Options"
          class="*:data-[selected=true]:text-accent-foreground w-fit *:h-6 *:w-fit *:px-3 *:text-sm *:font-normal"
        >
          <Tabs.Tab id="daily">Daily<Tabs.Indicator /></Tabs.Tab>
          <Tabs.Tab id="weekly">Weekly<Tabs.Indicator /></Tabs.Tab>
        </Tabs.List>
      </Tabs.ListContainer>
    </Tabs>
  );
}

CSS Classes

The Tabs component uses these CSS classes:

Base Classes

  • .tabs - Base tabs container
  • .tabs__list-container - Tab list container wrapper
  • .tabs__list-container__scroller - Scrollable wrapper around the tab list (handles overflow and fading edges)
  • .tabs__list-container__scroll-prev - Scroll chevron button for the previous (left / up) direction
  • .tabs__list-container__scroll-next - Scroll chevron button for the next (right / down) direction
  • .tabs__list - Tab list container
  • .tabs__tab - Individual tab button
  • .tabs__separator - Separator between tabs
  • .tabs__panel - Tab panel content
  • .tabs__indicator - Tab indicator

Orientation Attributes

  • .tabs[data-orientation="horizontal"] - Horizontal tab layout (default)
  • .tabs[data-orientation="vertical"] - Vertical tab layout

Variant Classes

  • .tabs--secondary - Secondary variant with underline indicator

Interactive States

The component supports both CSS pseudo-classes and data attributes:

  • Selected: [aria-selected="true"] or [data-selected="true"]
  • Hover: :hover or [data-hovered="true"]
  • Focus: :focus-visible
  • Disabled: [aria-disabled="true"] or [data-disabled="true"]

API Reference

Tabs Props

PropTypeDefaultDescription
variant"primary" | "secondary""primary"Visual style variant. Primary uses a filled indicator, secondary uses an underline indicator
orientation"horizontal" | "vertical""horizontal"Tab layout orientation
selectedKeystring-Controlled selected tab key
defaultSelectedKeystring-Default selected tab key
onSelectionChange(key: string) => void-Selection change handler
classstring-Additional CSS classes
asValidComponent"div"Overrides the default DOM element (Kobalte polymorphic as)

Tabs.ListContainer Props

PropTypeDefaultDescription
initialShadowbooleanfalseRender the end-edge fade and scroll chevron before scroll measurement (SSR / first frame). Set when the list is known to overflow, so they don't blink in after hydration.
hideScrollButtonsbooleanfalseHide the scroll chevron buttons when the list overflows (the fading edges remain).
classstring-Additional CSS classes

Tabs.List Props

PropTypeDefaultDescription
aria-labelstring-Accessibility label for tab list
classstring-Additional CSS classes

Tabs.Tab Props

PropTypeDefaultDescription
idstring-Unique tab identifier
isDisabledbooleanfalseWhether tab is disabled
classstring-Additional CSS classes
asValidComponent"button"Overrides the default DOM element (Kobalte polymorphic as)

Tabs.Separator Props

PropTypeDefaultDescription
classstring-Additional CSS classes

Tabs.Panel Props

PropTypeDefaultDescription
idstring-Panel identifier matching tab id
classstring-Additional CSS classes
asValidComponent"div"Overrides the default DOM element (Kobalte polymorphic as)

Differences from HeroUI React

  • Use onClick (Solid's native event) instead of React Aria's onPress.
  • Polymorphism uses Kobalte's as prop instead of React's render prop override.
  • onSelectionChange reports the key as a string.
  • Upstream renders a SelectionIndicator inside each Tabs.Tab; this port keeps that authoring shape (Kobalte has no selection-indicator primitive) and ports React Aria's shared-element transition so the indicator animates between tabs.
  • Tabs.ListContainer accepts initialShadow to server-render the overflow chevron and fade for lists known to overflow, and hideScrollButtons to omit the chevron buttons entirely.
  • The selected tab is kept in view: scrolled to instantly on mount, with an animated scroll on selection change.

Last updated: 7/19/26, 3:27 AM

HeroUI SolidUnofficial SolidJS port of HeroUI v3, built on Kobalte and @heroui/styles