Skip to main content

An accessible tab navigation component built with semantic HTML and Tailwind CSS. Organize content sections with horizontal tab bars and smooth panel switching. Supports boxed, bordered, lifted, and underline variants with active states for clean, zero-JavaScript tabbed interfaces.

Tab components organize content into separate sections that users can switch between without leaving the page. Built with semantic HTML and radio input patterns, tabs provide intuitive navigation for grouping related information. The Frutjam tabs system supports multiple sizes, styles, and layout directions—CSS-only or JavaScript-controlled—making it ideal for dashboards, documentation, and tabbed interfaces.

Class Type Description
tabsBaseContainer for a group of tab inputs and content panels
tabModifierIndividual tab radio input
tab-contentModifierContent panel associated with a tab
tab-activeModifierForces the active/selected state on a tab (for JS-controlled tabs)
tab-disabledModifierDisabled appearance for a tab
tabs-underlineStyleUnderline indicator style
tabs-liftedStyleCard-like lifted tab style
tabs-boxStyleBoxed pill-style tabs
tabs-verticalModifierVertical tab layout with tabs in a left-side column
tabs-endModifierAligns the tab row to the end (right in LTR)
tabs-bottomModifierPlaces tabs below the content panel
tabs-xsSizeExtra small tab padding
tabs-smSizeSmall tab padding
tabs-mdSizeMedium tab padding (default)
tabs-lgSizeLarge tab padding
tabs-xlSizeExtra large tab padding

Basic Usage

The default tabs use a simple underline-on-active style with radio inputs. Each tab input is paired with a tab-content panel immediately after it.

Overview content: a high-level summary of features, metrics, or status. This panel is visible by default.

Details content: in-depth information such as configuration options, data breakdown, or extended documentation.

Activity content: a timeline, event log, or recent changes relevant to this section.

html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<div class="tabs">
  <input type="radio" name="tabs-basic" class="tab" aria-label="Overview" checked>
  <div class="tab-content pt-4">
    <p>Overview content: a high-level summary of features, metrics, or status. This panel is visible by default.</p>
  </div>

  <input type="radio" name="tabs-basic" class="tab" aria-label="Details">
  <div class="tab-content pt-4">
    <p>Details content: in-depth information such as configuration options, data breakdown, or extended documentation.</p>
  </div>

  <input type="radio" name="tabs-basic" class="tab" aria-label="Activity">
  <div class="tab-content pt-4">
    <p>Activity content: a timeline, event log, or recent changes relevant to this section.</p>
  </div>
</div>

Tab Styles

Use tabs-underline, tabs-box, or tabs-lifted to change the visual style of the tab strip.

Underline style — a colored bottom border marks the active tab against a full-width separator line.
Details panel.
Activity panel.
Box style — the active tab gets a raised background inside a tinted pill container.
Details panel.
Activity panel.
Lifted style — the active tab appears as a card tab with borders that merge into the content panel below.
Details panel.
Activity panel.
html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<div class="tabs tabs-underline">
  <input type="radio" name="style-underline" class="tab" aria-label="Overview" checked>
  <div class="tab-content pt-4">Underline style — a colored bottom border marks the active tab against a full-width separator line.</div>
  <input type="radio" name="style-underline" class="tab" aria-label="Details">
  <div class="tab-content pt-4">Details panel.</div>
  <input type="radio" name="style-underline" class="tab" aria-label="Activity">
  <div class="tab-content pt-4">Activity panel.</div>
</div>

<div class="tabs tabs-box">
  <input type="radio" name="style-box" class="tab" aria-label="Overview" checked>
  <div class="tab-content pt-4">Box style — the active tab gets a raised background inside a tinted pill container.</div>
  <input type="radio" name="style-box" class="tab" aria-label="Details">
  <div class="tab-content pt-4">Details panel.</div>
  <input type="radio" name="style-box" class="tab" aria-label="Activity">
  <div class="tab-content pt-4">Activity panel.</div>
</div>

<div class="tabs tabs-lifted">
  <input type="radio" name="style-lifted" class="tab" aria-label="Overview" checked>
  <div class="tab-content p-4">Lifted style — the active tab appears as a card tab with borders that merge into the content panel below.</div>
  <input type="radio" name="style-lifted" class="tab" aria-label="Details">
  <div class="tab-content p-4">Details panel.</div>
  <input type="radio" name="style-lifted" class="tab" aria-label="Activity">
  <div class="tab-content p-4">Activity panel.</div>
</div>

Tab Sizes

Scale the tab padding from tabs-xs to tabs-xl to match the density of your layout.

Extra small tabs
Content
Small tabs
Content
Medium tabs (default)
Content
Large tabs
Content
Extra large tabs
Content
html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<div class="tabs tabs-underline tabs-xs">
  <input type="radio" name="size-xs" class="tab" aria-label="Extra Small" checked>
  <div class="tab-content pt-3">Extra small tabs</div>
  <input type="radio" name="size-xs" class="tab" aria-label="Tab 2">
  <div class="tab-content pt-3">Content</div>
</div>

<div class="tabs tabs-underline tabs-sm">
  <input type="radio" name="size-sm" class="tab" aria-label="Small" checked>
  <div class="tab-content pt-3">Small tabs</div>
  <input type="radio" name="size-sm" class="tab" aria-label="Tab 2">
  <div class="tab-content pt-3">Content</div>
</div>

<div class="tabs tabs-underline tabs-md">
  <input type="radio" name="size-md" class="tab" aria-label="Medium" checked>
  <div class="tab-content pt-3">Medium tabs (default)</div>
  <input type="radio" name="size-md" class="tab" aria-label="Tab 2">
  <div class="tab-content pt-3">Content</div>
</div>

<div class="tabs tabs-underline tabs-lg">
  <input type="radio" name="size-lg" class="tab" aria-label="Large" checked>
  <div class="tab-content pt-3">Large tabs</div>
  <input type="radio" name="size-lg" class="tab" aria-label="Tab 2">
  <div class="tab-content pt-3">Content</div>
</div>

<div class="tabs tabs-underline tabs-xl">
  <input type="radio" name="size-xl" class="tab" aria-label="Extra Large" checked>
  <div class="tab-content pt-3">Extra large tabs</div>
  <input type="radio" name="size-xl" class="tab" aria-label="Tab 2">
  <div class="tab-content pt-3">Content</div>
</div>

Tabs with Buttons

Combine tab with btn to render tabs as styled buttons. The btn class takes over colors and border-radius while the tab selection logic is preserved.

Home — your primary landing panel with quick access to key actions and a summary view.

Profile — manage personal information, preferences, and account details.

Settings — configure notifications, privacy options, and connected integrations.

html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<div class="tabs gap-2">
  <input type="radio" name="btn-tabs" class="tab btn btn-sm" aria-label="Home" checked>
  <div class="tab-content pt-4">
    <p>Home — your primary landing panel with quick access to key actions and a summary view.</p>
  </div>

  <input type="radio" name="btn-tabs" class="tab btn btn-sm" aria-label="Profile">
  <div class="tab-content pt-4">
    <p>Profile — manage personal information, preferences, and account details.</p>
  </div>

  <input type="radio" name="btn-tabs" class="tab btn btn-sm" aria-label="Settings">
  <div class="tab-content pt-4">
    <p>Settings — configure notifications, privacy options, and connected integrations.</p>
  </div>
</div>

Disabled Tabs

Add tab-disabled and the disabled attribute to prevent interaction with a tab.

This tab is active and accessible.
This content is not reachable.
Another available tab panel.
html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<div class="tabs tabs-underline">
  <input type="radio" name="disabled-tabs" class="tab" aria-label="Available" checked>
  <div class="tab-content pt-4">This tab is active and accessible.</div>

  <input type="radio" name="disabled-tabs" class="tab tab-disabled" aria-label="Unavailable" disabled>
  <div class="tab-content pt-4">This content is not reachable.</div>

  <input type="radio" name="disabled-tabs" class="tab" aria-label="Another">
  <div class="tab-content pt-4">Another available tab panel.</div>
</div>

Vertical Tabs

tabs-vertical switches to a grid layout with tabs in a left-side column and the content filling the right. A full-height separator line is automatically added between columns.

General Settings

Manage your display name, language preference, and time zone.

Security

Update your password, enable two-factor authentication, and review active sessions.

Notifications

Choose which events trigger email, push, or in-app notifications.

Billing

View invoices, update your payment method, and manage your subscription plan.

html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<div class="tabs tabs-vertical">
  <input type="radio" name="vertical" class="tab" aria-label="General" checked>
  <div class="tab-content px-6 py-2">
    <h3 class="font-semibold mb-1">General Settings</h3>
    <p>Manage your display name, language preference, and time zone.</p>
  </div>

  <input type="radio" name="vertical" class="tab" aria-label="Security">
  <div class="tab-content px-6 py-2">
    <h3 class="font-semibold mb-1">Security</h3>
    <p>Update your password, enable two-factor authentication, and review active sessions.</p>
  </div>

  <input type="radio" name="vertical" class="tab" aria-label="Notifications">
  <div class="tab-content px-6 py-2">
    <h3 class="font-semibold mb-1">Notifications</h3>
    <p>Choose which events trigger email, push, or in-app notifications.</p>
  </div>

  <input type="radio" name="vertical" class="tab" aria-label="Billing">
  <div class="tab-content px-6 py-2">
    <h3 class="font-semibold mb-1">Billing</h3>
    <p>View invoices, update your payment method, and manage your subscription plan.</p>
  </div>
</div>

Tabs at Bottom

tabs-bottom flips the layout so the content appears above the tab strip, useful for mobile-style navigation bars or bottom toolbars.

Feed — latest posts and updates from people you follow.

Explore — discover trending topics, creators, and recommended content.

Saved — articles and posts you bookmarked for later.

html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<div class="tabs tabs-bottom tabs-underline">
  <input type="radio" name="bottom-tabs" class="tab" aria-label="Feed" checked>
  <div class="tab-content pb-4">
    <p>Feed — latest posts and updates from people you follow.</p>
  </div>

  <input type="radio" name="bottom-tabs" class="tab" aria-label="Explore">
  <div class="tab-content pb-4">
    <p>Explore — discover trending topics, creators, and recommended content.</p>
  </div>

  <input type="radio" name="bottom-tabs" class="tab" aria-label="Saved">
  <div class="tab-content pb-4">
    <p>Saved — articles and posts you bookmarked for later.</p>
  </div>
</div>

Tabs Aligned to End

tabs-end pushes the tab strip to the right (end) of the container, useful for secondary navigation or action menus.

Weekly view — aggregated metrics for the past 7 days.
Monthly view — trend analysis over the past 30 days.
Yearly view — annual summary and year-over-year comparison.
html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<div class="tabs tabs-underline tabs-end">
  <input type="radio" name="end-tabs" class="tab" aria-label="Week" checked>
  <div class="tab-content pt-4">Weekly view — aggregated metrics for the past 7 days.</div>

  <input type="radio" name="end-tabs" class="tab" aria-label="Month">
  <div class="tab-content pt-4">Monthly view — trend analysis over the past 30 days.</div>

  <input type="radio" name="end-tabs" class="tab" aria-label="Year">
  <div class="tab-content pt-4">Yearly view — annual summary and year-over-year comparison.</div>
</div>

JavaScript-controlled Tabs

For non-radio patterns (e.g. anchor links, dynamic content), add tab-active to the selected tab in JavaScript. The paired tab-content is shown via the .tab-active + .tab-content selector.

Dashboard panel — visible because this tab has tab-active.
Reports panel.
Integrations panel.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<div class="tabs tabs-box" id="js-tabs">
  <button class="tab tab-active" data-tab="0">Dashboard</button>
  <div class="tab-content pt-4">Dashboard panel — visible because this tab has <code>tab-active</code>.</div>

  <button class="tab" data-tab="1">Reports</button>
  <div class="tab-content pt-4">Reports panel.</div>

  <button class="tab" data-tab="2">Integrations</button>
  <div class="tab-content pt-4">Integrations panel.</div>
</div>

<script>
  document.querySelectorAll('#js-tabs .tab').forEach(tab => {
    tab.addEventListener('click', () => {
      document.querySelectorAll('#js-tabs .tab').forEach(t => t.classList.remove('tab-active'));
      tab.classList.add('tab-active');
    });
  });
</script>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { useTabs } from 'frutjam/react'

export default function TabsDemo() {
  const tabs = useTabs(0)
  return (
    <div className="tabs tabs-box">
      <button {...tabs.tabProps(0)}>Dashboard</button>
      <div {...tabs.panelProps(0)} className="tab-content pt-4">Dashboard panel.</div>

      <button {...tabs.tabProps(1)}>Reports</button>
      <div {...tabs.panelProps(1)} className="tab-content pt-4">Reports panel.</div>

      <button {...tabs.tabProps(2)}>Integrations</button>
      <div {...tabs.panelProps(2)} className="tab-content pt-4">Integrations panel.</div>
    </div>
  )
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup>
import { ref } from 'vue'
const active = ref(0)
</script>

<template>
  <div class="tabs tabs-box">
    <button
      v-for="(label, i) in ['Dashboard', 'Reports', 'Integrations']"
      :key="i"
      class="tab"
      :class="{ 'tab-active': active === i }"
      :aria-selected="active === i"
      role="tab"
      @click="active = i"
    ></button>

    <div class="tab-content pt-4" :hidden="active !== 0">Dashboard panel.</div>
    <div class="tab-content pt-4" :hidden="active !== 1">Reports panel.</div>
    <div class="tab-content pt-4" :hidden="active !== 2">Integrations panel.</div>
  </div>
</template>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<script>
  let active = $state(0)
  const labels = ['Dashboard', 'Reports', 'Integrations']
</script>

<div class="tabs tabs-box">
  {#each labels as label, i}
    <button
      class="tab"
      class:tab-active={active === i}
      aria-selected={active === i}
      role="tab"
      onclick={() => active = i}
    >{label}</button>
    <div class="tab-content pt-4" hidden={active !== i}>{label} panel.</div>
  {/each}
</div>