Skip to content

UITabs

UITabs

A versatile tabs component that provides an interface for organizing and switching between different content sections, with support for custom styling, icons, and dynamic content.

<template>
<UITabs
v-model="activeTab"
:tabs="tabs"
:variant="'default'"
@change="handleTabChange"
>
<template #tab="{ tab }">
<div class="flex items-center space-x-2">
<component
v-if="tab.icon"
:is="tab.icon"
class="w-5 h-5"
/>
<span>{{ tab.label }}</span>
<UIBadge
v-if="tab.count"
size="sm"
variant="gray"
>
{{ tab.count }}
</UIBadge>
</div>
</template>
<template #panel="{ tab }">
<div class="p-4">
{{ tab.content }}
</div>
</template>
</UITabs>
</template>
<script setup lang="ts">
interface Tab {
id: string
label: string
icon?: any
count?: number
content?: string
}
const tabs = ref<Tab[]>([
{
id: 'details',
label: 'Details',
content: 'Details content'
},
{
id: 'settings',
label: 'Settings',
content: 'Settings content'
}
])
const activeTab = ref('details')
const handleTabChange = (tabId: string) => {
console.log('Tab changed:', tabId)
}
</script>

Props

  • modelValue (string): Active tab ID
  • tabs (array): Tab definitions
  • variant (string): Visual variant
  • vertical (boolean): Vertical layout
  • stretch (boolean): Full width tabs
  • disabled (boolean): Disable all tabs
  • size (string): Tab size
  • animated (boolean): Content animation
  • lazy (boolean): Lazy load panels

Events

  • update:modelValue: Active tab changed
  • change: Tab selection changed
  • click: Tab clicked
  • focus: Tab focused
  • blur: Tab blurred

Slots

  • tab: Custom tab content
  • panel: Custom panel content
  • prefix: Content before tabs
  • suffix: Content after tabs

Usage Examples

  1. Basic Tabs:
<template>
<UITabs
v-model="activeTab"
:tabs="tabs"
/>
</template>
<script setup>
const activeTab = ref('overview')
const tabs = [
{
id: 'overview',
label: 'Overview',
content: 'Overview content'
},
{
id: 'details',
label: 'Details',
content: 'Details content'
}
]
</script>
  1. Custom Tab Content:
<template>
<UITabs
v-model="activeView"
:tabs="viewTabs"
class="w-full"
>
<template #tab="{ tab }">
<div class="flex items-center space-x-3 px-4 py-2">
<component
:is="tab.icon"
:class="[
'w-5 h-5',
activeView === tab.id
? 'text-primary-600'
: 'text-gray-400'
]"
/>
<div>
<div class="font-medium">
{{ tab.label }}
</div>
<div class="text-sm text-gray-500">
{{ tab.description }}
</div>
</div>
<UIBadge
v-if="tab.count"
:variant="getBadgeVariant(tab)"
>
{{ tab.count }}
</UIBadge>
</div>
</template>
<template #panel="{ tab }">
<component
:is="tab.component"
v-bind="tab.props"
/>
</template>
</UITabs>
</template>
<script setup>
import {
HomeIcon,
UsersIcon,
CogIcon
} from '@gohighlevel/ghl-icons/24/outline'
const activeView = ref('dashboard')
const viewTabs = [
{
id: 'dashboard',
label: 'Dashboard',
description: 'Overview and stats',
icon: HomeIcon,
component: DashboardView
},
{
id: 'users',
label: 'Users',
description: 'Manage users',
icon: UsersIcon,
count: 12,
component: UsersView
},
{
id: 'settings',
label: 'Settings',
description: 'System settings',
icon: CogIcon,
component: SettingsView
}
]
const getBadgeVariant = (tab) => {
switch (tab.id) {
case 'users': return 'primary'
default: return 'gray'
}
}
</script>
  1. Vertical Tabs:
<template>
<div class="flex h-[600px]">
<UITabs
v-model="activeSection"
:tabs="sections"
:vertical="true"
class="w-64 border-r"
>
<template #tab="{ tab }">
<div class="flex items-center space-x-3 p-3">
<tab.icon class="w-5 h-5" />
<span>{{ tab.label }}</span>
</div>
</template>
<template #panel="{ tab }">
<div class="p-6">
<h2 class="text-lg font-medium mb-4">
{{ tab.label }}
</h2>
<component :is="tab.component" />
</div>
</template>
</UITabs>
</div>
</template>

Best Practices

  1. User Experience:

    • Clear labels
    • Visual feedback
    • Smooth transitions
    • Consistent layout
  2. Accessibility:

    • ARIA roles
    • Keyboard navigation
    • Focus management
    • Screen reader support
  3. Performance:

    • Lazy loading
    • Content caching
    • Efficient updates
    • Memory management
  4. Implementation:

    • State persistence
    • Error handling
    • Loading states
    • Dynamic content

Common Use Cases

  1. Settings Panel:
<template>
<UITabs
v-model="activeSettings"
:tabs="settingsTabs"
>
<template #panel="{ tab }">
<div class="space-y-6 p-6">
<h3 class="text-lg font-medium">
{{ tab.label }}
</h3>
<component
:is="tab.component"
v-model="settings[tab.id]"
@change="handleSettingChange"
/>
<div class="flex justify-end">
<UIButton
type="primary"
:loading="saving"
@click="saveSettings"
>
Save Changes
</UIButton>
</div>
</div>
</template>
</UITabs>
</template>
<script setup>
const settingsTabs = [
{
id: 'general',
label: 'General',
component: GeneralSettings
},
{
id: 'notifications',
label: 'Notifications',
component: NotificationSettings
},
{
id: 'security',
label: 'Security',
component: SecuritySettings
}
]
</script>
  1. Product Details:
<template>
<div class="product-details">
<UITabs
v-model="activeTab"
:tabs="productTabs"
class="w-full"
>
<template #tab="{ tab }">
<div class="flex items-center justify-center px-6 py-3">
<span>{{ tab.label }}</span>
<UIBadge
v-if="tab.count"
size="sm"
variant="gray"
class="ml-2"
>
{{ tab.count }}
</UIBadge>
</div>
</template>
<template #panel="{ tab }">
<div class="p-6">
<Suspense>
<component
:is="tab.component"
:product-id="productId"
/>
<template #fallback>
<UISkeleton :rows="5" />
</template>
</Suspense>
</div>
</template>
</UITabs>
</div>
</template>
  1. Form Wizard:
<template>
<div class="form-wizard">
<UITabs
v-model="currentStep"
:tabs="steps"
:disabled="true"
>
<template #tab="{ tab, index }">
<div
class="flex items-center"
:class="{
'text-primary-600': isStepComplete(index)
}"
>
<div
class="w-8 h-8 rounded-full flex items-center justify-center"
:class="getStepClass(index)"
>
<CheckIcon
v-if="isStepComplete(index)"
class="w-5 h-5"
/>
<span v-else>
{{ index + 1 }}
</span>
</div>
<span class="ml-2">{{ tab.label }}</span>
</div>
</template>
<template #panel="{ tab }">
<div class="p-6">
<component
:is="tab.component"
v-model="formData[tab.id]"
@valid="setStepValidity"
@complete="nextStep"
/>
<div class="flex justify-between mt-6">
<UIButton
v-if="currentStep !== 'step1'"
@click="previousStep"
>
Back
</UIButton>
<UIButton
v-if="currentStep !== lastStep"
type="primary"
:disabled="!isStepValid"
@click="nextStep"
>
Continue
</UIButton>
<UIButton
v-else
type="primary"
:disabled="!isFormValid"
@click="submitForm"
>
Submit
</UIButton>
</div>
</div>
</template>
</UITabs>
</div>
</template>