Vertical Tabs
Vue navigationSettings-page navigation pattern — labels stack on the left, content panel on the right. Same API as Tabs but with a vertical orientation. Use for dense, multi-section settings UIs.
Also available for React ->Installation
$ pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/vertical-tabs.json$ npx shadcn-vue@latest add https://uipkge.dev/r/vue/vertical-tabs.json$ yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/vertical-tabs.json$ bunx shadcn-vue@latest add https://uipkge.dev/r/vue/vertical-tabs.json
Or with the named registry:
npx shadcn-vue@latest add @uipkge/vertical-tabs
Examples
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
class | HTMLAttributes['class'] | — | optional |
Dependencies
Files (6)
-
app/components/ui/vertical-tabs/VerticalTabs.vue 0.7 kB
<script setup lang="ts"> import type { TabsRootEmits, TabsRootProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { TabsRoot, useForwardPropsEmits } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps< TabsRootProps & { class?: HTMLAttributes['class'] } >() const emits = defineEmits<TabsRootEmits>() const delegated = reactiveOmit(props, 'class', 'orientation') const forwarded = useForwardPropsEmits(delegated, emits) </script> <template> <TabsRoot v-bind="{ 'data-slot': 'vertical-tabs', orientation: 'vertical', ...forwarded }" :class="cn('flex w-full gap-6', props.class)" > <slot /> </TabsRoot> </template> -
app/components/ui/vertical-tabs/VerticalTabsList.vue 0.7 kB
<script setup lang="ts"> import type { TabsListProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { TabsList, useForwardProps } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps< TabsListProps & { class?: HTMLAttributes['class'] } >() const delegated = reactiveOmit(props, 'class') const forwarded = useForwardProps(delegated) </script> <template> <TabsList data-uipkge data-slot="vertical-tabs-list" v-bind="forwarded" :class="cn('border-border flex w-56 shrink-0 flex-col gap-0.5 border-r pr-3', props.class)" > <slot /> </TabsList> </template> -
app/components/ui/vertical-tabs/VerticalTabsSection.vue 0.5 kB
<script setup lang="ts"> import type { HTMLAttributes } from 'vue' import { cn } from '@/lib/utils' defineProps<{ label: string class?: HTMLAttributes['class'] }>() </script> <template> <div data-uipkge data-slot="vertical-tabs-section" :class=" cn( 'text-muted-foreground mt-3 mb-1 px-2 text-xs font-medium tracking-wider uppercase first:mt-0', $attrs.class as string, ) " > {{ label }} </div> </template> -
app/components/ui/vertical-tabs/VerticalTabsTrigger.vue 1.4 kB
<script setup lang="ts"> import { computed } from 'vue' import type { HTMLAttributes } from 'vue' import { TabsTrigger } from 'reka-ui' import { cn } from '@/lib/utils' interface Props { class?: HTMLAttributes['class'] value: string disabled?: boolean } const props = withDefaults(defineProps<Props>(), { disabled: false, }) const delegated = computed(() => { const { class: _, ...rest } = props return rest }) </script> <template> <TabsTrigger v-slot="slotProps" data-uipkge data-slot="vertical-tabs-trigger" v-bind="delegated" :class=" cn( 'group/trigger text-muted-foreground relative flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm font-medium transition-[color,background-color] duration-150', 'hover:bg-muted/60 hover:text-foreground', 'focus-visible:ring-ring/50 focus-visible:ring-2 focus-visible:outline-none', 'disabled:pointer-events-none disabled:opacity-50', 'data-[state=active]:bg-muted data-[state=active]:text-foreground', '[&>svg]:size-4 [&>svg]:shrink-0', props.class, ) " :disabled="props.disabled" > <span aria-hidden class="bg-primary absolute inset-y-1 left-0 w-0.5 rounded-full opacity-0 transition-opacity duration-150 group-data-[state=active]/trigger:opacity-100" /> <slot v-bind="slotProps" /> </TabsTrigger> </template> -
app/components/ui/vertical-tabs/VerticalTabsContent.vue 0.8 kB
<script setup lang="ts"> import { computed } from 'vue' import type { HTMLAttributes } from 'vue' import { TabsContent } from 'reka-ui' import { cn } from '@/lib/utils' interface Props { class?: HTMLAttributes['class'] value: string forceMount?: boolean } const props = defineProps<Props>() const delegated = computed(() => { const { class: _, ...rest } = props return rest }) </script> <template> <TabsContent v-slot="slotProps" data-uipkge data-slot="vertical-tabs-content" v-bind="delegated" :class=" cn( 'ring-offset-background focus-visible:ring-ring/50 flex-1 focus-visible:ring-2 focus-visible:outline-none', props.class, ) " :force-mount="props.forceMount" > <slot v-bind="slotProps" /> </TabsContent> </template> -
app/components/ui/vertical-tabs/index.ts 0.3 kB
export { default as VerticalTabs } from './VerticalTabs.vue' export { default as VerticalTabsList } from './VerticalTabsList.vue' export { default as VerticalTabsSection } from './VerticalTabsSection.vue' export { default as VerticalTabsTrigger } from './VerticalTabsTrigger.vue' export { default as VerticalTabsContent } from './VerticalTabsContent.vue'
Raw manifest: https://uipkge.dev/r/vue/vertical-tabs.json