// preview in
saved for every page

Tabs

Switches between views of the same thing, like the details, items and history of one order, without leaving the page. Only one panel shows at a time.

Built on Base UIPattern WAI-ARIA TabsClient componentSource ↗
minimal · light · #3fa34d
2 bags of Bourbon coffee beans and 1 hand grinder. Total $186.40.
orientation

Installation

Set up Camaleo once (Installation), then import what you need. It renders on the client.

import { Tabs, TabsList, Tab, TabsPanel } from "@camaleo/react";

Anatomy

A Tab and a TabsPanel with the same value belong together. The list is one stop in the Tab order; the arrow keys move between its tabs.

Examples

Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above. Click a tab, then try the arrow keys.

minimal · light · #3fa34d

Acme Store

Online store · created on 03/02/2025

Shown on invoices and in emails to customers.

Vertical

With orientation="vertical" the list stands beside the panel and ↑ ↓ move between tabs. It suits settings with more sections than fit in a row.

  • Order #4821 arrived damaged
  • Can I change my delivery address?

With icons and counts

A count tells people where the work is before they click. Keep it short: the number becomes part of the tab's name.

Webhooks: endpoints, parameters and sample responses.

Many tabs

When the tabs don't fit, the list scrolls sideways and keeps the active tab in view. Past eight or so, a sidebar or a select is easier to scan.

/orders/4821?tab=items

2 bags of Bourbon coffee beans and 1 hand grinder. Total $186.40.

In the URL

Click a tab and watch the address. With value and onValueChange you can keep the tab in the URL, so a link or a reload opens the same view.

Customer since 2023. 42 orders, $9,870.00 spent, last order on 10/21/2026.

Load on first open

Open Activity. A hidden panel isn't rendered, so you can fetch its data the first time it opens and show a skeleton meanwhile.

Revenue$12,480.00+12%

Follow focus

By default the arrow keys only move focus and Enter or Space opens the tab. Add activateOnFocus when panels show instantly, as these numbers do.

October so far: $12,480.00 received, $1,150.00 to receive.

Tax reports open after your first full month of sales, on 12/01/2026.

Disabled, with a reason

The arrow keys still reach a disabled tab but can't open it. Say why it's off and when it opens, linked with aria-describedby so it's read on focus.

In context

These are whole pieces of UI you can copy, in the skin you picked.

Hand coffee grinder

$89.90
4.6(128 reviews)

“Consistent grind and easy to clean. I use it every morning.”

Marina L., 10/02/2026

Product detailsCard · Tabs · DataList · Rating
Out for delivery

Order #4821

  • Bourbon coffee beans, 500 g$96.50
  • Hand coffee grinder$89.90
Order pageCard · Badge · Tabs · Timeline
Bruno Costabruno@acme.com.br
Bruno can
Team memberCard · Avatar · Tabs · Checkbox · Button

Guidelines

Customer since 2023. 42 orders, $9,870.00 spent, last order on 10/21/2026.
DoViews of one thing.

Summary, orders and notes all describe the same customer, and people can read them in any order.

2 items, $186.40.
Don’tSteps of a task.

A checkout has an order and a next step. Tabs let people skip ahead and hide how far they've come; use a stepper.

DoLinks for pages.

Moving to another page is navigation: links with aria-current="page" keep the back button, new tabs and shared URLs working.

Our coffee, gear and gift cards.
Don’tTabs as site navigation.

Screen readers announce a tab list, so people expect the content to change in place, not the whole page.

Props

PropTypeDefaultDescription
valueany—The value of the currently active Tab. Use when the component is controlled. When the value is null, no Tab will be active.
defaultValueany0The default value. Use when the component is not controlled. When the value is null, no Tab will be active.
orientationOrientation'horizontal'The component orientation (layout flow direction).
onValueChange(value, eventDetails) => void—Callback invoked when new value is being set. The event reason is 'none' for user-initiated changes, such as a click or keyboard navigation; 'initial' for the first automatic selection or fallback in uncontrolled roots when defaultValue is omitted or undefined, including when the implicit initial value is disabled or missing; 'disabled' for automatic fallback when the selected tab becomes disabled in uncontrolled roots; or 'missing' for automatic fallback when the selected tab is removed, or when an explicit defaultValue never matches a mounted tab in uncontrolled roots. For automatic changes, the selected value can be null when no enabled Tab is available as a fallback. Automatic changes cannot be canceled; calling eventDetails.cancel() for 'initial', 'disabled', or 'missing' has no effect.

Tab and TabsPanel take a value that pairs them. Tab also takes disabled.

TabsList decides how the keyboard behaves:

PropTypeDefaultDescription
activateOnFocusbooleanfalseWhether to automatically change the active tab on arrow key focus. Otherwise, tabs will be activated using <kbd>Enter</kbd> or <kbd>Space</kbd> key press.
loopFocusbooleantrueWhether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys.
Also accepts every prop of the underlying primitive and native attributes.

TabsPanel can stay in the page while hidden:

PropTypeDefaultDescription
keepMountedbooleanfalseWhether to keep the HTML element in the DOM while the panel is hidden.
Also accepts every prop of the underlying primitive and native attributes.

Styling hooks

Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the list itself.

Attributes

data-orientationhorizontal · vertical
data-activeon the active tab
data-disabledon a disabled tab
data-activation-directionleft · right · up · down, which way the selection moved

Tokens it reads

Color

--cml-border--cml-fg--cml-fg-muted--cml-fg-secondary--cml-focus-offset--cml-focus-ring--cml-focus-width--cml-radius-surface--cml-tabs-list-bg--cml-tabs-list-border--cml-tabs-list-shadow

Shape & size

--cml-border-width--cml-tabs-gap--cml-tabs-list-padding--cml-tabs-list-radius

Type & motion

--cml-duration-fast--cml-ease-standard--cml-font-control--cml-font-size-control--cml-font-weight-control--cml-letter-spacing-control--cml-text-transform-control

This component only

--cml-tab-active-bg--cml-tab-active-fg--cml-tab-active-shadow--cml-tab-radius
/* pill tabs on a tinted track, only in this card */
.billing [data-slot="tabs-list"] {
  --cml-tabs-list-bg: var(--cml-surface-sunken);
  --cml-tabs-list-radius: var(--cml-radius-surface);
  --cml-tab-radius: var(--cml-radius-surface);
  --cml-tab-active-bg: var(--cml-surface-raised);
}

Accessibility

TabMoves focus into the list, to the active tab. Pressed again, it moves to the panel.
← / →Move focus to the previous or next tab, wrapping at the ends. In a vertical list, ↑ and ↓.
Home / EndMove focus to the first or last tab.
Enter / SpaceOpens the focused tab. With activateOnFocus, the tab opens as soon as it gets focus.
Was this page helpful? · Edit this page on GitHub