Components
Tabs
A set of layered sections of content—known as tab panels—that are displayed one at a time.
Source Code
This consists of 3 components:
- Tabs - The parent component that holds the tabs and tab content.
- Tab - The tab component that holds the tab title.
- TabContent - The tab content component that holds the tab content.
Copy the following code into your project:
Tabs
<template>
<HTabGroup v-bind="($attrs, $props)" v-slot="{ selectedIndex }" @change="emits('change', $event)">
<HTabList>
<slot name="tab" :selectedIndex="selectedIndex"></slot>
</HTabList>
<HTabPanels v-slot="{ selectedIndex }">
<slot name="content" :selectedIndex="selectedIndex"></slot>
</HTabPanels>
</HTabGroup>
</template>
<script setup lang="ts">
const props = withDefaults(
defineProps<{
/**
* The element to render as.
* @default div
*/
as?: string;
/**
* The default selected index
* @default 0
*/
defaultIndex?: number;
/**
* The selected index if using as controlled component
* @default null
*/
selectedIndex?: number;
/**
* Whether the tablist should be vertical
* @default false
*/
vertical?: boolean;
/**
* Whether the tabpanel should be manually viewed when cycling through the tabs with keyboard.
* Users would have to press Enter or Space to vie data if this is set to true
* @default false
*/
manual?: boolean;
}>(),
{
as: "div",
defaultIndex: 0,
vertical: false,
manual: false,
}
);
const emits = defineEmits<{
(event: "change", index: number): void;
}>();
</script>
Tab
<template>
<HTab
v-bind="($attrs, $props)"
:disabled="disabled"
:class="cn(variants({ type, class: props.class }))"
v-slot="{ selected }"
>
<slot :selected="selected" />
</HTab>
</template>
<script setup lang="ts">
import { cva, type VariantProps } from "class-variance-authority";
const variants = cva(
"inline-flex items-center justify-center gap-2 text-sm focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed",
{
variants: {
type: {
underline:
"z-[1] whitespace-nowrap border-b-2 border-b-transparent px-4 py-3 text-foreground/70 transition focus:outline-none data-[headlessui-state=selected]:border-primary data-[headlessui-state=selected]:text-primary hover:text-primary hover:bg-muted",
fill: "z-[1] whitespace-nowrap rounded-md px-4 py-2 text-muted-foreground transition hover:text-foreground data-[headlessui-state=selected]:bg-background data-[headlessui-state=selected]:text-foreground data-[headlessui-state=selected]:shadow-sm font-medium",
},
},
defaultVariants: {
type: "underline",
},
}
);
type Props = VariantProps<typeof variants>;
const props = withDefaults(
defineProps<{
/**
* The component to render as.
* @default button
*/
as?: string;
/**
* Whether the tab is disabled.
* @default false
*/
disabled?: boolean;
/**
* The type of tab to render.
* @default underline
*/
type?: Props["type"];
/**
* The class to apply to the tab.
*/
class?: any;
}>(),
{ as: "button", disabled: false }
);
</script>
TabContent
<template>
<HTabPanel
v-bind="($attrs, $props)"
class="rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-border focus-visible:ring-offset-2 focus-visible:ring-offset-background"
v-slot="{ selected }"
>
<slot :selected="selected"></slot>
</HTabPanel>
</template>
<script setup lang="ts">
withDefaults(
defineProps<{
/**
* The component to render as.
* @default div
*/
as?: string;
/**
* Whether the element should ignore the selected index.
* @default false
*/
static?: boolean;
/**
* Whether the tabpanel should be unmounted when not visible.
* @default true
*/
unmount?: boolean;
}>(),
{
as: "div",
static: false,
unmount: true,
}
);
</script>
Usage
Types
As you can see in the preview above, the type being used was the fill
type. I aslo created an underline
type. You can create your own types by adding them to the type
object in the Tab
component.
Table of contents