Tabs

A set of layered sections of content—known as tab panels—that are displayed one at a time.

Examples

Basic

PropDefaultTypeDescription
content-stringSet the tooltip content.
disabled-booleanSet to disable the tooltip.
Preview
Code
We'll never share your email with anyone else.

Variant and Color

PropDefaultTypeDescription
tabssoft-blackstringSet the tabs variant and color.
_tabsTrigger.tabs-stringSet the tabs variant and color via _tabsTrigger.
Preview
Code
We'll never share your email with anyone else.

Disabled

PropDefaultTypeDescription
disabledfalsebooleanSet the tabs disabled.
_tabsTrigger.disabled-booleanSet the tabs disabled via _tabsTrigger.
Preview
Code

Size

PropDefaultTypeDescription
size-stringSet the tabs size.
_tabsTrigger.size-stringSet the trigger size.
_tabsContent.size-stringSet the content size.
Preview
Code
Tab 1 content
Tab 2 content

Slots

NamePropsDescription
listitemsThe list slot.
trigger-The trigger slot.
contentitemThe content slot.

Presets

shortcuts/tabs.ts
type TabsPrefix = 'tabs'

export const staticTabs: Record<`${TabsPrefix}-${string}` | TabsPrefix, string> = {
  // configurations
  'tabs': 'transition-colors duration-200 ease-out',
  'tabs-default-variant': 'tabs-soft-black',
  'tabs-disabled': 'n-disabled',

  // components
  'tabs-root': 'flex flex-col w-full',
  'tabs-trigger': 'w-full focus-visible:z-10',
  'tabs-list': 'flex bg-muted items-center justify-center rounded-md p-1 w-full',
  'tabs-content': 'mt-4 text-base',
}

export const dynamicTabs = [
  [/^tabs-([^-]+)-([^-]+)$/, ([, v = 'solid', c = 'primary']) => `data-[state=active]:btn-${v}-${c}`],
]

export const tabs = [
  ...dynamicTabs,
  staticTabs,
]

Props

types/tabs.ts
import type { TabsContentProps, TabsListProps, TabsRootProps, TabsTriggerProps } from 'radix-vue'
import type { HTMLAttributes } from 'vue'
import type { NButtonProps } from './button'

interface BaseExtensions {
  class?: HTMLAttributes['class']
  size?: HTMLAttributes['class']
}

export interface NTabsProps extends TabsRootProps, BaseExtensions, Pick<NTabsTriggerProps, 'tabs' | 'disabled'> {
  /**
   * The array of items that is passed to tabs.
   *
   * @default []
   */
  items: any[]

  // sub-components
  _tabsContent?: Partial<NTabsContentProps>
  _tabsTrigger?: Partial<NTabsTriggerProps>
  _tabsList?: Partial<NTabsListProps>
}

export interface NTabsRootProps extends TabsRootProps, BaseExtensions {
  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/tabs.ts
   */
  una?: {
    // components
    tabsRoot?: HTMLAttributes['class']
  }
}

export interface NTabsListProps extends TabsListProps, BaseExtensions {
  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/tabs.ts
   */
  una?: {
    // components
    tabsList?: HTMLAttributes['class']
  }
}

export interface NTabsTriggerProps extends TabsTriggerProps, Omit<NButtonProps, 'una' | 'size'>, BaseExtensions {
  /**
   * Allows you to add `UnaUI` button preset properties,
   * Think of it as a shortcut for adding options or variants to the preset if available.
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/tabs.ts
   * @example
   * tabs="solid-green"
   */
  tabs?: string
  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/tabs.ts
   */
  una?: {
    // components
    tabsTrigger?: HTMLAttributes['class']
    tabsDefaultVariant?: HTMLAttributes['class']
  } & NButtonProps['una']
}

export interface NTabsContentProps extends TabsContentProps, BaseExtensions {
  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/tabs.ts
   */
  una?: {
    // components
    tabsContent?: HTMLAttributes['class']
  }
}

Components

Tabs.vue
TabsRoot.vue
TabsList.vue
TabsTrigger.vue
TabsContent.vue
<script setup lang="ts">
import type { TabsRootEmits } from 'radix-vue'
import type { NTabsProps } from '../../../types/tabs'
import { useForwardPropsEmits } from 'radix-vue'
import { computed } from 'vue'
import { omitProps } from '../../../utils'
import TabsContent from './TabsContent.vue'
import TabsList from './TabsList.vue'
import TabsRoot from './TabsRoot.vue'
import TabsTrigger from './TabsTrigger.vue'

const props = defineProps<NTabsProps>()
const emits = defineEmits<TabsRootEmits>()

const delegatedProps = computed(() => {
  const { class: _, ...delegated } = props

  return delegated
})

const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>

<template>
  <TabsRoot
    v-bind="omitProps(forwarded, ['items', 'tabs', 'disabled'])"
    :default-value="defaultValue"
  >
    <TabsList v-bind="forwarded._tabsList">
      <slot name="list" :items="items">
        <template
          v-for="item in items"
          :key="item.value"
        >
          <TabsTrigger
            :tabs="item?._tabsTrigger?.tabs || item.tabs || props.tabs"
            :disabled="item?._tabsTrigger?.disabled ?? item.disabled ?? props.disabled"
            :value="item.value"
            v-bind="{ ...forwarded._tabsTrigger, ...item?._tabsTrigger }"
          >
            <slot name="trigger" :item="item" :disabled="item?._tabsTrigger?.disabled ?? item.disabled ?? props.disabled ?? false">
              {{ item.name }}
            </slot>
          </TabsTrigger>
        </template>
      </slot>
    </TabsList>
    <template
      v-for="item in items"
      :key="item.value"
    >
      <TabsContent v-bind="forwarded._tabsContent" :value="item.value">
        <slot name="content" :item="item">
          <component :is="typeof item.content === 'string' ? 'span' : item.content">
            {{ typeof item.content === 'string' ? item.content : '' }}
          </component>
        </slot>
      </TabsContent>
    </template>
  </TabsRoot>
</template>