🔥 Oxide UI v0.1.0 — Dark mode only, copy-paste ready. Get started →
Skip to content

Tabs

<div role="tablist">

Organises content into panels accessible via a horizontal tab bar. Two visual styles: underline and pill.

Overview

Preview
Content for Overview tab.

Underline tabs

Preview
html
<div class="flex border-b border-zinc-800">
  <!-- Active tab -->
  <button class="px-4 py-2.5 text-sm font-medium border-b-2 border-[#D40C37] text-[#D40C37] -mb-px focus:outline-none">
    Overview
  </button>
  <!-- Inactive tab -->
  <button class="px-4 py-2.5 text-sm font-medium text-zinc-500 hover:text-zinc-300 transition-colors focus:outline-none">
    Analytics
  </button>
</div>

Pill tabs

Preview
html
<div class="flex gap-1 rounded-lg bg-zinc-900 p-1">
  <!-- Active -->
  <button class="rounded-md px-3 py-1.5 text-sm font-medium bg-zinc-700 text-zinc-100 shadow-sm focus:outline-none">
    Day
  </button>
  <!-- Inactive -->
  <button class="rounded-md px-3 py-1.5 text-sm font-medium text-zinc-500 hover:text-zinc-300 transition-colors focus:outline-none">
    Week
  </button>
</div>

With Vue reactivity

vue
<script setup>
import { ref } from 'vue'
const tabs = ['Overview', 'Analytics', 'Settings']
const active = ref('Overview')
</script>

<template>
  <div>
    <div class="flex border-b border-zinc-800">
      <button
        v-for="tab in tabs"
        :key="tab"
        @click="active = tab"
        :class="['px-4 py-2.5 text-sm font-medium transition-colors focus:outline-none',
          active === tab ? 'border-b-2 border-[#D40C37] text-[#D40C37] -mb-px' : 'text-zinc-500 hover:text-zinc-300']"
      >{{ tab }}</button>
    </div>
    <div class="mt-4 p-4 rounded-lg border border-zinc-800 bg-zinc-900/40 text-sm text-zinc-400">
      Content for {{ active }}
    </div>
  </div>
</template>
vue
<script setup>
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue'
</script>

<template>
  <TabGroup>
    <TabList class="flex border-b border-zinc-800">
      <Tab v-for="tab in ['Overview', 'Analytics']" :key="tab" v-slot="{ selected }" as="template">
        <button :class="['px-4 py-2.5 text-sm font-medium transition-colors focus:outline-none -mb-px',
          selected ? 'border-b-2 border-[#D40C37] text-[#D40C37]' : 'text-zinc-500 hover:text-zinc-300']">
          {{ tab }}
        </button>
      </Tab>
    </TabList>
    <TabPanels class="mt-4">
      <TabPanel>Overview content</TabPanel>
      <TabPanel>Analytics content</TabPanel>
    </TabPanels>
  </TabGroup>
</template>

Accessibility notes

  • Use role="tablist" on the container, role="tab" on each button, role="tabpanel" on each panel
  • Link tabs to panels with aria-controls and id
  • Active tab should have aria-selected="true"
  • Headless UI's <TabGroup> handles all of this automatically

Released under the MIT License.