Skip to content

Tab Component ​

A lightweight, accessible tab navigation component with v-model support. Renders a horizontal tab bar with active-state styling, optional separators between tabs, and named slots for fully custom tab content.


Table of Contents ​


Import ​

ts
import { Tab } from "dolphin-components";
import type { TabOption, TabProps } from "dolphin-components";

Basic Usage ​

vue
<template>
  <Tab v-model="activeTab" :tabs="tabs" />

  <div v-if="activeTab === 'overview'">Overview content</div>
  <div v-if="activeTab === 'details'">Details content</div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { Tab } from "dolphin-components";

const activeTab = ref("overview");

const tabs = [
  { id: "overview", name: "Overview" },
  { id: "details", name: "Details" },
];
</script>

Props ​

PropTypeDefaultDescription
modelValuestring—Required. The id of the currently active tab.
tabsTabOption[][]Array of tab objects, each with at least id and name.
showSeparatorbooleantrueShow a vertical divider line between tab buttons.

Events ​

EventPayloadDescription
update:modelValuestringEmitted when a tab is clicked. Supports v-model.
changestringEmitted alongside update:modelValue when a tab is clicked. Useful for side-effect handlers.

Slots ​

The component provides dynamic named slots based on each tab's id. This allows you to fully customize the label content rendered inside the tab button.

Slot NameScope PropsDescription
[tab.id]{ tab: TabOption, active: boolean }Custom content for the tab button with that id. Falls back to tab.name text if the slot is not provided.

Example with custom slot:

vue
<Tab v-model="activeTab" :tabs="tabs">
  <template #report="{ tab, active }">
    <span class="flex items-center gap-1">
      <Icons name="FileText" :size="14" />
      {{ tab.name }}
    </span>
  </template>
</Tab>

TypeScript Interfaces ​

typescript
interface TabOption {
  id: string;        // Unique identifier for the tab
  name: string;      // Display label for the tab
  [key: string]: any; // Extra properties (passed through slot scope)
}

interface TabProps {
  modelValue: string;      // Currently active tab id
  tabs: TabOption[];       // Array of tab definitions
  showSeparator?: boolean; // Show divider between tabs (default: true)
}

Usage Examples ​

Without Separators ​

vue
<template>
  <Tab v-model="activeTab" :tabs="tabs" :showSeparator="false" />
</template>

<script setup lang="ts">
import { ref } from "vue";
import { Tab } from "dolphin-components";

const activeTab = ref("general");
const tabs = [
  { id: "general", name: "General" },
  { id: "security", name: "Security" },
  { id: "notifications", name: "Notifications" },
];
</script>

With Custom Slot Content ​

Render icons, badges, or any custom markup inside the tab button using the dynamic named slot.

vue
<template>
  <Tab v-model="activeTab" :tabs="tabs">
    <template #users="{ tab, active }">
      <span class="flex items-center gap-1.5">
        <Icons name="Users" :size="14" />
        {{ tab.name }}
        <span
          class="bg-blue-100 text-blue-700 text-[11px] px-1.5 rounded-full"
        >
          {{ tab.count }}
        </span>
      </span>
    </template>

    <template #settings="{ active }">
      <span class="flex items-center gap-1">
        <Icons name="Settings" :size="14" />
        Settings
      </span>
    </template>
  </Tab>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { Tab, Icons } from "dolphin-components";
import type { TabOption } from "dolphin-components";

const activeTab = ref("users");

const tabs: TabOption[] = [
  { id: "users", name: "Users", count: 42 },
  { id: "roles", name: "Roles" },
  { id: "settings", name: "Settings" },
];
</script>

Listening to Tab Changes ​

vue
<template>
  <Tab v-model="activeTab" :tabs="tabs" @change="onTabChange" />
</template>

<script setup lang="ts">
import { ref } from "vue";
import { Tab } from "dolphin-components";

const activeTab = ref("report");

const tabs = [
  { id: "report", name: "Report" },
  { id: "print", name: "Print" },
];

const onTabChange = (tabId: string) => {
  console.log("Switched to tab:", tabId);
  // Fetch data, update state, etc.
};
</script>

Best Practices ​

  1. Use unique id values — Each tab's id must be unique within the array. The id is used as the v-model value and the slot name.
  2. Keep tab counts reasonable — The component renders horizontally. Use 2–6 tabs for the best UX. For more items, consider a different navigation pattern.
  3. Leverage the change event for side effects — Use @change for actions like fetching data. Use v-model for reactive state binding.
  4. Use named slots sparingly — The default text rendering is clean and consistent. Only use custom slots when you need icons, badges, or other rich content.

Troubleshooting ​

Tab does not highlight when clicked ​

  • Ensure v-model is bound to a ref variable.
  • Verify that the modelValue matches one of the tab id values exactly.

Custom slot not rendering ​

  • The slot name must match the tab's id exactly (case-sensitive).
  • Example: If the tab id is "report", the slot must be #report.

Separator not visible ​

  • The separator is a 1px gray vertical line. It only appears when showSeparator is true (the default) and there are 2 or more tabs.