Skip to content

Tabs

Organizes content into multiple sections, displaying only one section at a time while keeping others easily accessible through a tabbed navigation interface. Tabs help structure information without overwhelming the user.

Usage

Use tabs to group related content or functionality, allowing users to switch between sections without leaving the current view.

Behavior

A tab can carry one action, such as a close button, by placing x-h-tab-action on a <span> inside it.

When the tabs outgrow their list, the list scrolls in place with no visible scrollbar, and the edge that hides more tabs fades out to show where the overflow is. The selected tab is brought into view automatically, both when it first renders and when the selection changes.

Keyboard Handling

The tab list is a single Tab stop that lands on the selected tab, so reaching the content does not mean tabbing past every other tab. Once a tab has focus:

  • Right / Left - Moves focus to the next or previous tab, wrapping around at both ends. Horizontal orientation only.
  • Down / Up - Moves focus to the next or previous tab, wrapping around at both ends. Vertical orientation only.
  • Home / End - Moves focus to the first or last tab.
  • Enter / Space - Activates the focused tab.

Tabs disabled with the native disabled attribute are skipped. Tabs with aria-disabled="true" are still reached by the arrows, Home and End, and are announced as unavailable, but neither a click nor Enter activates them. Moving focus does not change the selection, so the tabs can be looked through before one is activated.

A tab action shares its tab's place in the tab order, so Tab from the focused tab moves to its action, where Enter / Space activates it. Only the action of the tab holding the tab stop is reachable this way, which keeps the tab list a single Tab stop.

Accessibility

The tab list, tabs and panels get their tablist, tab and tabpanel roles automatically, and the list mirrors data-orientation as aria-orientation so the navigation axis is announced correctly.

Selection stays yours to drive. Bind aria-selected on each tab and hidden on each panel to the same value. A tab written without aria-selected is announced as not selected. The roving tab stop follows the selected tab, so Tab always enters the widget at the tab whose panel is showing.

Each tab needs an id and an aria-controls pointing at its panel, and each panel an id and an aria-labelledby pointing back at its tab, which is what pairs the two for a screen reader. A tab must also live inside a tab list, and a tab list inside a tabs root. The component throws when any of this is missing.

A tab action holds only whatever you put inside it, usually an icon, so it needs its own aria-label or aria-labelledby to be announced as anything more than a button. Activating it does not change the selection.

Panels are focusable by default, so Tab moves from the selected tab into its panel and the panel shows a focus ring. That stop is what makes a panel with no focusable content of its own reachable. When your panel already contains something focusable, that element is the way in, so set tabindex="-1" on the panel to avoid a redundant stop.

API Reference

Component attribute(s)

x-h-tabs
x-h-tab-bar
x-h-tab-list
x-h-tab
x-h-tab-action
x-h-tab-list-actions
x-h-tab-list-action
x-h-tabs-content

Attributes

x-h-tabs

AttributeTypeRequiredDescription
data-orientationhorizontal
vertical
trueChanges the orientation of the tab list

x-h-tab-bar

AttributeTypeRequiredDescription
data-floatingbooleanfalseFloating style tab list.
data-sizedefault
sm
lg
falseHeight of the tab bar. Ignored when the tab bar is floating or the orientation is vertical.

x-h-tab

AttributeTypeRequiredDescription
idstringtrueIdentifies the tab. The panel's aria-labelledby must point at it.
aria-controlsstringtrueThe id of the tab content this tab shows.
aria-selectedbooleanfalseMarks the tab as selected. Bind it to your own state. Defaults to false when it is left out.
aria-disabledbooleanfalseMarks the tab unavailable while keeping it focusable and announced. It can be reached with the keyboard but not activated.

x-h-tab-action

Must be applied to a <span> element. A tab can only have one action.

AttributeTypeRequiredDescription
aria-labelstringtrue*Accessible label for the action
aria-labelledbystringtrue*References an element whose text labels the action

INFO

One of aria-label or aria-labelledby is required.

x-h-tabs-content

AttributeTypeRequiredDescription
idstringtrueThe id the tab's aria-controls points at.
aria-labelledbystringtrueThe id of the tab that labels this content.
hiddenbooleanfalseShow/hide the tab content
tabindexstringfalseDefaults to 0 so the panel is reachable. Set it to -1 when the panel already contains focusable content, to avoid a redundant stop.

x-h-tab-list-action

AttributeTypeRequiredDescription
data-variantoutline
transparent
falseChanges the style of the button. Default is outline.

Modifiers

x-h-tab-list-actions

ModifierDescription
endTab action will be placed at the end of the tab list container.

Examples

Sizes

html
<div x-h-tabs data-orientation="horizontal">
  <div x-h-tab-bar data-size="sm">
    <div x-h-tab-list>
      <button id="smt1" x-h-tab aria-controls="smt1c" aria-selected="true">Tab 1</button>
      <button id="smt2" x-h-tab aria-controls="smt2c">Tab 2</button>
    </div>
  </div>
  <div x-h-tabs-content id="smt1c" aria-labelledby="smt1" hidden></div>
  <div x-h-tabs-content id="smt2c" aria-labelledby="smt2" hidden></div>
</div>
<div x-h-tabs data-orientation="horizontal">
  <div x-h-tab-bar>
    <div x-h-tab-list>
      <button id="dt1" x-h-tab aria-controls="dt1c" aria-selected="true">Tab 1</button>
      <button id="dt2" x-h-tab aria-controls="dt2c">Tab 2</button>
    </div>
  </div>
  <div x-h-tabs-content id="dt1c" aria-labelledby="dt1" hidden></div>
  <div x-h-tabs-content id="dt2c" aria-labelledby="dt2" hidden></div>
</div>
<div x-h-tabs data-orientation="horizontal">
  <div x-h-tab-bar data-size="lg">
    <div x-h-tab-list>
      <button id="lgt1" x-h-tab aria-controls="lgt1c" aria-selected="true">Tab 1</button>
      <button id="lgt2" x-h-tab aria-controls="lgt2c">Tab 2</button>
    </div>
  </div>
  <div x-h-tabs-content id="lgt1c" aria-labelledby="lgt1" hidden></div>
  <div x-h-tabs-content id="lgt2c" aria-labelledby="lgt2" hidden></div>
</div>

Disabled tabs

Tab 2 uses the native disabled attribute, so the arrow keys step straight over it. Tab 3 uses aria-disabled, so the arrows land on it and it is announced as unavailable, but it cannot be activated.

html
<div x-h-tabs data-orientation="horizontal">
  <div x-h-tab-bar>
    <div x-h-tab-list>
      <button id="dis1" x-h-tab aria-controls="dis1c" aria-selected="true">Tab 1</button>
      <button id="dis2" x-h-tab aria-controls="dis2c" disabled>Tab 2</button>
      <button id="dis3" x-h-tab aria-controls="dis3c" aria-disabled="true">Tab 3</button>
      <button id="dis4" x-h-tab aria-controls="dis4c">Tab 4</button>
    </div>
  </div>
  <div x-h-tabs-content id="dis1c" aria-labelledby="dis1"></div>
  <div x-h-tabs-content id="dis2c" aria-labelledby="dis2" hidden></div>
  <div x-h-tabs-content id="dis3c" aria-labelledby="dis3" hidden></div>
  <div x-h-tabs-content id="dis4c" aria-labelledby="dis4" hidden></div>
</div>

Scrollable tab content

html
<div x-h-tabs data-orientation="horizontal" style="height:10rem">
  <div x-h-tab-bar>
    <div x-h-tab-list>
      <button id="stce" x-h-tab aria-controls="stcec" aria-selected="true">Tab 1</button>
    </div>
  </div>
  <div class="relative" x-h-tabs-content id="stcec" aria-labelledby="stce">
    <div class="absolute inset-0 overflow-auto">
      <img src="/harmonia/logo/harmonia.svg" alt="@harmonia" width="240px" />
    </div>
  </div>
</div>

Horizontal tabs

html
<div x-h-tabs data-orientation="horizontal" x-data="{ activeTabId: 'hit1' }">
  <div x-h-tab-bar>
    <div x-h-tab-list>
      <button x-h-tab id="hit1" aria-controls="hit1c" :aria-selected="activeTabId === 'hit1'" @click="activeTabId = 'hit1'">Tab 1</button>
      <button x-h-tab id="hit2" aria-controls="hit2c" :aria-selected="activeTabId === 'hit2'" @click="activeTabId = 'hit2'">Tab 2</button>
      <button x-h-tab id="hit3" aria-controls="hit3c" :aria-selected="activeTabId === 'hit3'" @click="activeTabId = 'hit3'">Tab 3</button>
    </div>
  </div>
  <div x-h-tabs-content id="hit1c" aria-labelledby="hit1" :hidden="activeTabId !== 'hit1'">
    <div class="p-2">Tab 1 Content</div>
  </div>
  <div x-h-tabs-content id="hit2c" aria-labelledby="hit2" :hidden="activeTabId !== 'hit2'">
    <div class="p-2">Tab 2 Content</div>
  </div>
  <div x-h-tabs-content id="hit3c" aria-labelledby="hit3" :hidden="activeTabId !== 'hit3'">
    <div class="p-2">Tab 3 Content</div>
  </div>
</div>

Overflowing tabs

When the tabs do not fit, the list scrolls in place and the edge hiding more tabs fades out. Tab 8 starts selected, so the list opens scrolled to it.

html
<div x-h-tabs data-orientation="horizontal" x-data="{ tabs: Array.from({ length: 12 }, (_, i) => `ovh${i + 1}`), activeTabId: 'ovh8' }">
  <div x-h-tab-bar>
    <div x-h-tab-list>
      <template x-for="(id, i) in tabs" :key="id">
        <button x-h-tab :id="id" :aria-controls="`${id}c`" :aria-selected="activeTabId === id" @click="activeTabId = id" x-text="`Tab ${i + 1}`"></button>
      </template>
    </div>
  </div>
  <template x-for="(id, i) in tabs" :key="id">
    <div x-h-tabs-content :id="`${id}c`" :aria-labelledby="id" :hidden="activeTabId !== id">
      <div class="p-2" x-text="`Tab ${i + 1} Content`"></div>
    </div>
  </template>
</div>

Overflowing tabs with actions

The action buttons keep their place in the bar while the list scrolls independently. Adding a tab selects it, so the list scrolls to reveal it.

html
<div
  x-h-tabs
  data-orientation="horizontal"
  x-data="{
    count: 12,
    tabs: Array.from({ length: 12 }, (_, i) => `ova${i + 1}`),
    activeTabId: 'ova1',
    add() {
      this.tabs.push(`ova${++this.count}`);
      this.activeTabId = `ova${this.count}`;
    },
  }"
>
  <div x-h-tab-bar>
    <div x-h-tab-list>
      <template x-for="(id, i) in tabs" :key="id">
        <button x-h-tab :id="id" :aria-controls="`${id}c`" :aria-selected="activeTabId === id" @click="activeTabId = id" x-text="`Tab ${i + 1}`"></button>
      </template>
    </div>
    <div x-h-tab-list-actions>
      <button x-h-tab-list-action data-variant="transparent" aria-label="add tab button" @click="add()">
        <svg x-h-lucide role="presentation" data-lucide="plus"></svg>
      </button>
    </div>
  </div>
  <template x-for="(id, i) in tabs" :key="id">
    <div x-h-tabs-content :id="`${id}c`" :aria-labelledby="id" :hidden="activeTabId !== id">
      <div class="p-2" x-text="`Tab ${i + 1} Content`"></div>
    </div>
  </template>
</div>

Horizontal tabs with icon and close button

html
<div
  x-h-tabs
  data-orientation="horizontal"
  x-data="{
    tabs: [{ id: 'hib1', title: 'Tab 1' }, { id: 'hib2', title: 'Tab 2' }, { id: 'hib3', title: 'Tab 3' }],
    activeTabId: 'hib1',
    close(id) {
      const index = this.tabs.findIndex((tab) => tab.id === id);
      this.tabs.splice(index, 1);
      if (this.activeTabId === id) this.activeTabId = this.tabs[Math.min(index, this.tabs.length - 1)]?.id;
    },
  }"
>
  <div x-h-tab-bar>
    <div x-h-tab-list>
      <template x-for="tab in tabs" :key="tab.id">
        <button x-h-tab :id="tab.id" :aria-controls="`${tab.id}c`" :aria-selected="activeTabId === tab.id" @click="activeTabId = tab.id">
          <svg x-h-lucide role="presentation" data-lucide="file"></svg>
          <span x-text="tab.title"></span>
          <span x-h-tab-action :aria-label="`close ${tab.title}`" @click="close(tab.id)">
            <svg x-h-icon data-icon="close" role="presentation"></svg>
          </span>
        </button>
      </template>
    </div>
  </div>
  <template x-for="tab in tabs" :key="tab.id">
    <div x-h-tabs-content :id="`${tab.id}c`" :aria-labelledby="tab.id" :hidden="activeTabId !== tab.id">
      <div class="p-2" x-text="`${tab.title} Content`"></div>
    </div>
  </template>
</div>

Horizontal tabs with actions

html
<div x-h-tabs data-orientation="horizontal">
  <div x-h-tab-bar>
    <div x-h-tab-list>
      <button x-h-tab id="hitwa1" aria-controls="hitwa1c" aria-selected="true">
        Tab 1
        <span x-h-tab-action aria-label="close tab">
          <svg x-h-icon data-icon="close" role="presentation"></svg>
        </span>
      </button>
    </div>
    <div x-h-tab-list-actions>
      <button x-h-tab-list-action data-variant="transparent" aria-label="add tab button">
        <svg x-h-lucide role="presentation" data-lucide="plus"></svg>
      </button>
    </div>
  </div>
  <div x-h-tabs-content id="hitwa1c" aria-labelledby="hitwa1">
    <div class="p-2">Tab 1 Content</div>
  </div>
</div>

Horizontal tabs with actions (end)

html
<div x-h-tabs data-orientation="horizontal">
  <div x-h-tab-bar>
    <div x-h-tab-list>
      <button x-h-tab id="hitwae1" aria-controls="hitwae1c" aria-selected="true">
        Tab 1
        <span x-h-tab-action aria-label="close tab">
          <svg x-h-icon data-icon="close" role="presentation"></svg>
        </span>
      </button>
    </div>
    <div x-h-tab-list-actions.end>
      <button x-h-tab-list-action aria-label="tab bar menu button">
        <svg x-h-lucide role="presentation" data-lucide="ellipsis"></svg>
      </button>
    </div>
  </div>
  <div x-h-tabs-content id="hitwae1c" aria-labelledby="hitwae1">
    <div class="p-2">Tab 1 Content</div>
  </div>
</div>

Horizontal float tabs

html
<div x-h-tabs data-orientation="horizontal" x-data="{ activeTabId: 'ht1' }">
  <div x-h-tab-bar data-floating="true">
    <div x-h-tab-list>
      <button x-h-tab id="ht1" aria-controls="ht1c" :aria-selected="activeTabId === 'ht1'" @click="activeTabId = 'ht1'">Tab 1</button>
      <button x-h-tab id="ht2" aria-controls="ht2c" :aria-selected="activeTabId === 'ht2'" @click="activeTabId = 'ht2'">Tab 2</button>
      <button x-h-tab id="ht3" aria-controls="ht3c" :aria-selected="activeTabId === 'ht3'" @click="activeTabId = 'ht3'">Tab 3</button>
    </div>
  </div>
  <div x-h-tabs-content id="ht1c" aria-labelledby="ht1" :hidden="activeTabId !== 'ht1'">
    <div class="p-2">Tab 1 Content</div>
  </div>
  <div x-h-tabs-content id="ht2c" aria-labelledby="ht2" :hidden="activeTabId !== 'ht2'">
    <div class="p-2">Tab 2 Content</div>
  </div>
  <div x-h-tabs-content id="ht3c" aria-labelledby="ht3" :hidden="activeTabId !== 'ht3'">
    <div class="p-2">Tab 3 Content</div>
  </div>
</div>

Horizontal float tabs that fit to size

You can make the tab bar fit to the size of the tab list by adding the w-max class.

html
<div x-h-tabs data-orientation="horizontal">
  <div x-h-tab-bar data-floating="true" class="w-max">
    <div x-h-tab-list>
      <button x-h-tab id="htfts1" aria-controls="htfts1c" aria-selected="true">Sign In</button>
      <button x-h-tab id="htfts2" aria-controls="htfts2c">Sign Up</button>
    </div>
  </div>
  <div x-h-tabs-content id="htfts1c" aria-labelledby="htfts1">
    <div class="p-2">Sign In</div>
  </div>
  <div x-h-tabs-content id="htfts2c" aria-labelledby="htfts2" hidden="true">
    <div class="p-2">Sign Up</div>
  </div>
</div>

Horizontal float tabs with icon and close button

html
<div x-h-tabs data-orientation="horizontal" x-data="{ activeTabId: 'hbt1' }">
  <div x-h-tab-bar data-floating="true">
    <div x-h-tab-list>
      <button x-h-tab id="hbt1" aria-controls="hbt1c" :aria-selected="activeTabId === 'hbt1'" @click="activeTabId = 'hbt1'">
        <svg x-h-lucide role="presentation" data-lucide="file"></svg>
        Tab 1
        <span x-h-tab-action aria-label="close tab">
          <svg x-h-icon data-icon="close" role="presentation"></svg>
        </span>
      </button>
      <button x-h-tab id="hbt2" aria-controls="hbt2c" :aria-selected="activeTabId === 'hbt2'" @click="activeTabId = 'hbt2'">
        <svg x-h-lucide role="presentation" data-lucide="file"></svg>
        Tab 2
        <span x-h-tab-action aria-label="close tab">
          <svg x-h-icon data-icon="close" role="presentation"></svg>
        </span>
      </button>
      <button x-h-tab id="hbt3" aria-controls="hbt3c" :aria-selected="activeTabId === 'hbt3'" @click="activeTabId = 'hbt3'">
        <svg x-h-lucide role="presentation" data-lucide="file"></svg>
        Tab 3
        <span x-h-tab-action aria-label="close tab">
          <svg x-h-icon data-icon="close" role="presentation"></svg>
        </span>
      </button>
    </div>
  </div>
  <div x-h-tabs-content id="hbt1c" aria-labelledby="hbt1" :hidden="activeTabId !== 'hbt1'">
    <div class="p-2">Tab 1 Content</div>
  </div>
  <div x-h-tabs-content id="hbt2c" aria-labelledby="hbt2" :hidden="activeTabId !== 'hbt2'">
    <div class="p-2">Tab 2 Content</div>
  </div>
  <div x-h-tabs-content id="hbt3c" aria-labelledby="hbt3" :hidden="activeTabId !== 'hbt3'">
    <div class="p-2">Tab 3 Content</div>
  </div>
</div>

Horizontal float tabs with actions

html
<div x-h-tabs data-orientation="horizontal">
  <div x-h-tab-bar data-floating="true">
    <div x-h-tab-list>
      <button x-h-tab id="hftwa1" aria-controls="hftwa1c" aria-selected="true">
        Tab 1
        <span x-h-tab-action aria-label="close tab">
          <svg x-h-icon data-icon="close" role="presentation"></svg>
        </span>
      </button>
    </div>
    <div x-h-tab-list-actions>
      <button x-h-tab-list-action data-variant="transparent" aria-label="add tab button">
        <svg x-h-lucide role="presentation" data-lucide="plus"></svg>
      </button>
    </div>
  </div>
  <div x-h-tabs-content id="hftwa1c" aria-labelledby="hftwa1">
    <div class="p-2">Tab 1 Content</div>
  </div>
</div>

Horizontal float tabs with actions (end)

html
<div x-h-tabs data-orientation="horizontal">
  <div x-h-tab-bar data-floating="true">
    <div x-h-tab-list>
      <button x-h-tab id="hftwae1" aria-controls="hftwae1c" aria-selected="true">
        Tab 1
        <span x-h-tab-action aria-label="close tab">
          <svg x-h-icon data-icon="close" role="presentation"></svg>
        </span>
      </button>
    </div>
    <div x-h-tab-list-actions.end>
      <button x-h-tab-list-action aria-label="menu button">
        <svg x-h-lucide role="presentation" data-lucide="ellipsis"></svg>
      </button>
    </div>
  </div>
  <div x-h-tabs-content id="hftwae1c" aria-labelledby="hftwae1">
    <div class="p-2">Tab 1 Content</div>
  </div>
</div>

Vertical tabs

html
<div x-h-tabs data-orientation="vertical" x-data="{ activeTabId: 'vit1' }">
  <div x-h-tab-bar>
    <div x-h-tab-list>
      <button x-h-tab id="vit1" aria-controls="vit1c" :aria-selected="activeTabId === 'vit1'" @click="activeTabId = 'vit1'">Tab 1</button>
      <button x-h-tab id="vit2" aria-controls="vit2c" :aria-selected="activeTabId === 'vit2'" @click="activeTabId = 'vit2'">Tab 2</button>
      <button x-h-tab id="vit3" aria-controls="vit3c" :aria-selected="activeTabId === 'vit3'" @click="activeTabId = 'vit3'">Tab 3</button>
    </div>
  </div>
  <div x-h-tabs-content id="vit1c" aria-labelledby="vit1" :hidden="activeTabId !== 'vit1'">
    <div class="p-2">Tab 1 Content</div>
  </div>
  <div x-h-tabs-content id="vit2c" aria-labelledby="vit2" :hidden="activeTabId !== 'vit2'">
    <div class="p-2">Tab 2 Content</div>
  </div>
  <div x-h-tabs-content id="vit3c" aria-labelledby="vit3" :hidden="activeTabId !== 'vit3'">
    <div class="p-2">Tab 3 Content</div>
  </div>
</div>

Overflowing vertical tabs

A vertical list scrolls once something constrains its height, here a fixed height on the root. The edge hiding more tabs fades out, and Tab 6 starts selected, so the list opens scrolled to it.

html
<div x-h-tabs data-orientation="vertical" style="height: 8rem" x-data="{ tabs: Array.from({ length: 10 }, (_, i) => `ovv${i + 1}`), activeTabId: 'ovv6' }">
  <div x-h-tab-bar>
    <div x-h-tab-list>
      <template x-for="(id, i) in tabs" :key="id">
        <button x-h-tab :id="id" :aria-controls="`${id}c`" :aria-selected="activeTabId === id" @click="activeTabId = id" x-text="`Tab ${i + 1}`"></button>
      </template>
    </div>
  </div>
  <template x-for="(id, i) in tabs" :key="id">
    <div x-h-tabs-content :id="`${id}c`" :aria-labelledby="id" :hidden="activeTabId !== id">
      <div class="p-2" x-text="`Tab ${i + 1} Content`"></div>
    </div>
  </template>
</div>

Vertical tabs with icon and close button

html
<div x-h-tabs data-orientation="vertical" x-data="{ activeTabId: 'vib1' }">
  <div x-h-tab-bar>
    <div x-h-tab-list>
      <button x-h-tab id="vib1" aria-controls="vib1c" :aria-selected="activeTabId === 'vib1'" @click="activeTabId = 'vib1'">
        <svg x-h-lucide role="presentation" data-lucide="file"></svg>
        Tab 1
        <span x-h-tab-action aria-label="close tab">
          <svg x-h-icon data-icon="close" role="presentation"></svg>
        </span>
      </button>
      <button x-h-tab id="vib2" aria-controls="vib2c" :aria-selected="activeTabId === 'vib2'" @click="activeTabId = 'vib2'">
        <svg x-h-lucide role="presentation" data-lucide="file"></svg>
        Tab 2
        <span x-h-tab-action aria-label="close tab">
          <svg x-h-icon data-icon="close" role="presentation"></svg>
        </span>
      </button>
      <button x-h-tab id="vib3" aria-controls="vib3c" :aria-selected="activeTabId === 'vib3'" @click="activeTabId = 'vib3'">
        <svg x-h-lucide role="presentation" data-lucide="file"></svg>
        Tab 3
        <span x-h-tab-action aria-label="close tab">
          <svg x-h-icon data-icon="close" role="presentation"></svg>
        </span>
      </button>
    </div>
  </div>
  <div x-h-tabs-content id="vib1c" aria-labelledby="vib1" :hidden="activeTabId !== 'vib1'">
    <div class="p-2">Tab 1 Content</div>
  </div>
  <div x-h-tabs-content id="vib2c" aria-labelledby="vib2" :hidden="activeTabId !== 'vib2'">
    <div class="p-2">Tab 2 Content</div>
  </div>
  <div x-h-tabs-content id="vib3c" aria-labelledby="vib3" :hidden="activeTabId !== 'vib3'">
    <div class="p-2">Tab 3 Content</div>
  </div>
</div>

Vertical tabs with actions

html
<div x-h-tabs data-orientation="vertical" style="height:8rem">
  <div x-h-tab-bar>
    <div x-h-tab-list>
      <button x-h-tab id="vitwa1" aria-controls="vitwa1c" aria-selected="true">
        Tab 1
        <span x-h-tab-action aria-label="close tab">
          <svg x-h-icon data-icon="close" role="presentation"></svg>
        </span>
      </button>
    </div>
    <div x-h-tab-list-actions>
      <button x-h-tab-list-action data-variant="transparent" aria-label="add tab button">
        <svg x-h-lucide role="presentation" data-lucide="plus"></svg>
      </button>
    </div>
  </div>
  <div x-h-tabs-content id="vitwa1c" aria-labelledby="vitwa1">
    <div class="p-2">Tab 1 Content</div>
  </div>
</div>

Vertical tabs with actions (end)

html
<div x-h-tabs data-orientation="vertical" style="height:8rem">
  <div x-h-tab-bar>
    <div x-h-tab-list>
      <button x-h-tab id="vitwae1" aria-controls="vitwae1c" aria-selected="true">
        Tab 1
        <span x-h-tab-action aria-label="close tab">
          <svg x-h-icon data-icon="close" role="presentation"></svg>
        </span>
      </button>
    </div>
    <div x-h-tab-list-actions.end>
      <button x-h-tab-list-action aria-label="menu button">
        <svg x-h-lucide role="presentation" data-lucide="ellipsis"></svg>
      </button>
    </div>
  </div>
  <div x-h-tabs-content id="vitwae1c" aria-labelledby="vitwae1">
    <div class="p-2">Tab 1 Content</div>
  </div>
</div>

Vertical float tabs

html
<div x-h-tabs data-orientation="vertical" x-data="{ activeTabId: 'vt1' }">
  <div x-h-tab-bar data-floating="true">
    <div x-h-tab-list>
      <button x-h-tab id="vt1" aria-controls="vt1c" :aria-selected="activeTabId === 'vt1'" @click="activeTabId = 'vt1'">Tab 1</button>
      <button x-h-tab id="vt2" aria-controls="vt2c" :aria-selected="activeTabId === 'vt2'" @click="activeTabId = 'vt2'">Tab 2</button>
      <button x-h-tab id="vt3" aria-controls="vt3c" :aria-selected="activeTabId === 'vt3'" @click="activeTabId = 'vt3'">Tab 3</button>
    </div>
  </div>
  <div x-h-tabs-content id="vt1c" aria-labelledby="vt1" :hidden="activeTabId !== 'vt1'">
    <div class="p-2">Tab 1 Content</div>
  </div>
  <div x-h-tabs-content id="vt2c" aria-labelledby="vt2" :hidden="activeTabId !== 'vt2'">
    <div class="p-2">Tab 2 Content</div>
  </div>
  <div x-h-tabs-content id="vt3c" aria-labelledby="vt3" :hidden="activeTabId !== 'vt3'">
    <div class="p-2">Tab 3 Content</div>
  </div>
</div>

Vertical float tabs with icon and close button

html
<div x-h-tabs data-orientation="vertical" x-data="{ activeTabId: 'vbt1' }">
  <div x-h-tab-bar data-floating="true">
    <div x-h-tab-list>
      <button x-h-tab id="vbt1" aria-controls="vbt1c" :aria-selected="activeTabId === 'vbt1'" @click="activeTabId = 'vbt1'">
        <svg x-h-lucide role="presentation" data-lucide="file"></svg>
        Tab 1
        <span x-h-tab-action aria-label="close tab">
          <svg x-h-icon data-icon="close" role="presentation"></svg>
        </span>
      </button>
      <button x-h-tab id="vbt2" aria-controls="vbt2c" :aria-selected="activeTabId === 'vbt2'" @click="activeTabId = 'vbt2'">
        <svg x-h-lucide role="presentation" data-lucide="file"></svg>
        Tab 2
        <span x-h-tab-action aria-label="close tab">
          <svg x-h-icon data-icon="close" role="presentation"></svg>
        </span>
      </button>
      <button x-h-tab id="vbt3" aria-controls="vbt3c" :aria-selected="activeTabId === 'vbt3'" @click="activeTabId = 'vbt3'">
        <svg x-h-lucide role="presentation" data-lucide="file"></svg>
        Tab 3
        <span x-h-tab-action aria-label="close tab">
          <svg x-h-icon data-icon="close" role="presentation"></svg>
        </span>
      </button>
    </div>
  </div>
  <div x-h-tabs-content id="vbt1c" aria-labelledby="vbt1" :hidden="activeTabId !== 'vbt1'">
    <div class="p-2">Tab 1 Content</div>
  </div>
  <div x-h-tabs-content id="vbt2c" aria-labelledby="vbt2" :hidden="activeTabId !== 'vbt2'">
    <div class="p-2">Tab 2 Content</div>
  </div>
  <div x-h-tabs-content id="vbt3c" aria-labelledby="vbt3" :hidden="activeTabId !== 'vbt3'">
    <div class="p-2">Tab 3 Content</div>
  </div>
</div>

Vertical float tabs with actions

html
<div x-h-tabs data-orientation="vertical" style="height:8rem">
  <div x-h-tab-bar data-floating="true">
    <div x-h-tab-list>
      <button x-h-tab id="vftwa1" aria-controls="vftwa1c" aria-selected="true">
        Tab 1
        <span x-h-tab-action aria-label="close tab">
          <svg x-h-icon data-icon="close" role="presentation"></svg>
        </span>
      </button>
    </div>
    <div x-h-tab-list-actions>
      <button x-h-tab-list-action data-variant="transparent" aria-label="add tab button">
        <svg x-h-lucide role="presentation" data-lucide="plus"></svg>
      </button>
    </div>
  </div>
  <div x-h-tabs-content id="vftwa1c" aria-labelledby="vftwa1">
    <div class="p-2">Tab 1 Content</div>
  </div>
</div>

Vertical float tabs with actions (end)

html
<div x-h-tabs data-orientation="vertical" style="height:8rem">
  <div x-h-tab-bar data-floating="true">
    <div x-h-tab-list>
      <button x-h-tab id="vftwae1" aria-controls="vftwae1c" aria-selected="true">
        Tab 1
        <span x-h-tab-action aria-label="close tab">
          <svg x-h-icon data-icon="close" role="presentation"></svg>
        </span>
      </button>
    </div>
    <div x-h-tab-list-actions.end>
      <button x-h-tab-list-action data-variant="outline" aria-label="menu button">
        <svg x-h-lucide role="presentation" data-lucide="ellipsis"></svg>
      </button>
    </div>
  </div>
  <div x-h-tabs-content id="vftwae1c" aria-labelledby="vftwae1">
    <div class="p-2">Tab 1 Content</div>
  </div>
</div>