Skip to content

@vibe-labs/design-components-tabs

Tabs component tokens, styles, and TypeScript types for the Vibe Design System.

Installation

css
@import "@vibe-labs/design-components-tabs";
ts
import { TabsVariants } from "@vibe-labs/design-components-tabs/types";
import type { TabsStyleProps, TabStyleProps } from "@vibe-labs/design-components-tabs/types";

Contents

Tokens (tabs.css)

CSS custom properties defined in @layer vibe.tokens:

Tab List

TokenDefault
--tabs-border-color--border-subtle
--tabs-border-width--border-width-1
--tabs-gap0

Tab Item

TokenDefaultDescription
--tab-height2.5remTab height
--tab-px--space-4Horizontal padding
--tab-font-size--text-smFont size
--tab-font-weight400Default weight
--tab-color--text-secondaryDefault text color
--tab-hover-color--text-primaryHover text color
--tab-hover-bg--surface-hover-overlayHover background
--tab-active-color--text-primaryActive text color
--tab-active-font-weight--font-semiboldActive weight

Indicator

TokenDefault
--tab-indicator-height2px
--tab-indicator-color--color-accent
--tab-indicator-radius--radius-full

Pill Variant

TokenDefault
--tab-pill-radius--radius-full
--tab-pill-bgtransparent
--tab-pill-active-bg--surface-elevated
--tab-pill-active-shadow--shadow-xs

Panel

TokenDefault
--tab-panel-padding--space-4

Generated Styles (tabs.g.css)

Component classes generated into @layer vibe.components.

Tab List

Flex row with bottom border. Active tab indicator sits precisely on the border via negative offset.

Modifiers

  • scrollable — horizontal overflow scroll with hidden scrollbars
  • full — tabs stretch evenly with flex: 1

Tab Item

Interactive inline-flex button with hover/focus/disabled states. aria-selected="true" activates the accent underline indicator via ::after.

Tab Slots

  • tab-icon — leading icon (accent-colored when selected)
  • tab-badge — trailing muted badge

Pill Variant

Completely swaps visual model: no bottom border, contained background with rounded corners, elevated surface + shadow on active tab, indicator hidden.

Tab Panel

Content area with padding. Supports [hidden] attribute for inactive panels.

TypeScript Types (types/)

ts
TabsVariants // ["pill"]
type TabsVariant

interface TabsStyleProps { variant?: TabsVariant; scrollable?: boolean; full?: boolean }
interface TabStyleProps { disabled?: boolean }
interface TabIconStyleProps {}
interface TabBadgeStyleProps {}
interface TabPanelStyleProps {}

Dist Structure

FileDescription
index.cssBarrel — imports tokens + generated styles
tabs.cssToken definitions
tabs.g.cssGenerated component styles
index.js / index.d.tsTypeScript types + runtime constants

Dependencies

Requires tokens from @vibe-labs/design (colors, surfaces, borders, spacing, typography, transitions, elevation).

Build

bash
npm run build

Usage Guide

Import

css
@import "@vibe-labs/design-components-tabs";
ts
import { TabsVariants } from "@vibe-labs/design-components-tabs/types";
import type { TabsStyleProps, TabStyleProps } from "@vibe-labs/design-components-tabs/types";

Variants

Attribute / FlagValuesNotes
data-variantpillPill style (no underline, elevated active)
data-scrollableboolean flagHorizontal scroll with hidden scrollbar
data-fullboolean flagTabs stretch evenly (flex: 1)
aria-selectedtrue on tabActivates accent underline indicator
disabledon tabReduced opacity, pointer-events: none

Examples

Basic tabs with panels

html
<div class="tabs" role="tablist">
  <button class="tab" role="tab" aria-selected="true" aria-controls="panel-overview">Overview</button>
  <button class="tab" role="tab" aria-selected="false" aria-controls="panel-details">Details</button>
  <button class="tab" role="tab" aria-selected="false" aria-controls="panel-history">History</button>
</div>

<div class="tab-panel" id="panel-overview" role="tabpanel">Overview content</div>
<div class="tab-panel" id="panel-details" role="tabpanel" hidden>Details content</div>
<div class="tab-panel" id="panel-history" role="tabpanel" hidden>History content</div>

Tabs with icons and badges

html
<div class="tabs" role="tablist">
  <button class="tab" role="tab" aria-selected="true">
    <svg class="tab-icon" aria-hidden="true"><!-- icon --></svg>
    Inbox
    <span class="tab-badge">12</span>
  </button>
  <button class="tab" role="tab" aria-selected="false">
    <svg class="tab-icon" aria-hidden="true"><!-- icon --></svg>
    Sent
  </button>
  <button class="tab" role="tab" aria-selected="false" disabled>
    <svg class="tab-icon" aria-hidden="true"><!-- icon --></svg>
    Archived
  </button>
</div>

Pill variant

html
<div class="tabs" data-variant="pill" role="tablist">
  <button class="tab" role="tab" aria-selected="true">Monthly</button>
  <button class="tab" role="tab" aria-selected="false">Quarterly</button>
  <button class="tab" role="tab" aria-selected="false">Yearly</button>
</div>

Full-width tabs

html
<div class="tabs" data-full role="tablist">
  <button class="tab" role="tab" aria-selected="true">Tab One</button>
  <button class="tab" role="tab" aria-selected="false">Tab Two</button>
  <button class="tab" role="tab" aria-selected="false">Tab Three</button>
</div>

Scrollable tabs (many items)

html
<div class="tabs" data-scrollable role="tablist">
  <button class="tab" role="tab" aria-selected="true">January</button>
  <button class="tab" role="tab" aria-selected="false">February</button>
  <button class="tab" role="tab" aria-selected="false">March</button>
  <button class="tab" role="tab" aria-selected="false">April</button>
  <button class="tab" role="tab" aria-selected="false">May</button>
  <button class="tab" role="tab" aria-selected="false">June</button>
</div>

With Vue

vue
<template>
  <div class="tabs" :data-variant="variant || undefined" :data-full="full || undefined" role="tablist">
    <button
      v-for="tab in tabs"
      :key="tab.id"
      class="tab"
      role="tab"
      :aria-selected="activeTab === tab.id"
      :disabled="tab.disabled"
      @click="activeTab = tab.id"
    >
      <component v-if="tab.icon" :is="tab.icon" class="tab-icon" />
      {{ tab.label }}
      <span v-if="tab.badge" class="tab-badge">{{ tab.badge }}</span>
    </button>
  </div>
  <div v-for="tab in tabs" :key="tab.id" class="tab-panel" :hidden="activeTab !== tab.id" role="tabpanel">
    <component :is="tab.panel" />
  </div>
</template>

Vibe