diff --git a/docs/app/components/content/examples/tabs/TabsOverflowCollapseExample.vue b/docs/app/components/content/examples/tabs/TabsOverflowCollapseExample.vue
new file mode 100644
index 0000000000..0a8b027a9a
--- /dev/null
+++ b/docs/app/components/content/examples/tabs/TabsOverflowCollapseExample.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
diff --git a/docs/app/components/content/examples/tabs/TabsOverflowScrollExample.vue b/docs/app/components/content/examples/tabs/TabsOverflowScrollExample.vue
new file mode 100644
index 0000000000..9df404f919
--- /dev/null
+++ b/docs/app/components/content/examples/tabs/TabsOverflowScrollExample.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
diff --git a/docs/app/components/content/examples/tabs/TabsOverflowWrapExample.vue b/docs/app/components/content/examples/tabs/TabsOverflowWrapExample.vue
new file mode 100644
index 0000000000..2c17996b0c
--- /dev/null
+++ b/docs/app/components/content/examples/tabs/TabsOverflowWrapExample.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
diff --git a/docs/content/docs/2.components/tabs.md b/docs/content/docs/2.components/tabs.md
index 720e79a6cd..c488bd318a 100644
--- a/docs/content/docs/2.components/tabs.md
+++ b/docs/content/docs/2.components/tabs.md
@@ -217,6 +217,30 @@ props:
---
::
+### Overflow :badge{label="Soon" class="align-text-top"}
+
+Use the `overflow` prop to control how the tab list handles items that don't fit in the available space:
+
+- `scroll`{lang="ts-type"} enables scrolling along the list axis.
+- `wrap`{lang="ts-type"} allows tabs to wrap onto multiple lines.
+- `collapse`{lang="ts-type"} hides overflowing tabs behind a **More** dropdown.
+
+When omitted, no overflow handling is applied.
+
+### Scroll
+
+:component-example{name="tabs-overflow-scroll-example"}
+
+### Wrap
+
+:component-example{name="tabs-overflow-wrap-example"}
+
+### Collapse
+
+Use the `more-label` and `more-icon` props to customize the overflow trigger. You can also use the `#more` slot to fully customize it.
+
+:component-example{name="tabs-overflow-collapse-example"}
+
## Examples
### Control active item
diff --git a/playgrounds/nuxt/app/pages/components/tabs.vue b/playgrounds/nuxt/app/pages/components/tabs.vue
index df0c013107..1322d1f57a 100644
--- a/playgrounds/nuxt/app/pages/components/tabs.vue
+++ b/playgrounds/nuxt/app/pages/components/tabs.vue
@@ -4,6 +4,7 @@ import theme from '#build/ui/tabs'
const colors = Object.keys(theme.variants.color)
const variants = Object.keys(theme.variants.variant)
const orientations = Object.keys(theme.variants.orientation)
+const overflows = ['none', ...Object.keys(theme.variants.overflow)]
const sizes = Object.keys(theme.variants.size)
const attrs = reactive({
@@ -13,6 +14,7 @@ const attrs = reactive({
})
const orientation = ref('horizontal' as keyof typeof theme.variants.orientation)
+const overflow = ref<'none' | keyof typeof theme.variants.overflow>('none')
const items = [{
label: 'Tab1',
@@ -31,6 +33,21 @@ const items = [{
slot: 'custom' as const,
badge: '300'
}]
+
+const manyItems = [
+ { label: 'Overview', icon: 'i-lucide-layout-dashboard', content: 'Overview content' },
+ { label: 'Activity', icon: 'i-lucide-activity', content: 'Activity content' },
+ { label: 'Settings', icon: 'i-lucide-settings', content: 'Settings content' },
+ { label: 'Members', icon: 'i-lucide-users', content: 'Members content' },
+ { label: 'Billing', icon: 'i-lucide-credit-card', content: 'Billing content' },
+ { label: 'Integrations', icon: 'i-lucide-plug', content: 'Integrations content' },
+ { label: 'Notifications', icon: 'i-lucide-bell', content: 'Notifications content' },
+ { label: 'Security', icon: 'i-lucide-shield', content: 'Security content' },
+ { label: 'API Keys', icon: 'i-lucide-key', content: 'API Keys content' },
+ { label: 'Logs', icon: 'i-lucide-scroll-text', content: 'Logs content' }
+]
+
+const overflowProp = computed(() => overflow.value === 'none' ? undefined : overflow.value)
@@ -39,6 +56,7 @@ const items = [{
+
@@ -59,5 +77,13 @@ const items = [{
Custom: {{ item.content }}
+
+
diff --git a/src/runtime/components/Tabs.vue b/src/runtime/components/Tabs.vue
index 3ec9724610..496f4e0c02 100644
--- a/src/runtime/components/Tabs.vue
+++ b/src/runtime/components/Tabs.vue
@@ -4,7 +4,7 @@ import type { ComponentPublicInstance, VNode } from 'vue'
import type { TabsRootProps, TabsRootEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import theme from '#build/ui/tabs'
-import type { AvatarProps, BadgeProps, IconProps } from '../types'
+import type { AvatarProps, BadgeProps, IconProps, DropdownMenuItem } from '../types'
import type { DynamicSlots, GetItemKeys } from '../types/utils'
import type { ComponentConfig } from '../types/tv'
@@ -56,10 +56,30 @@ export interface TabsProps extends Pick extends Pick {}
+export interface TabsEmits extends TabsRootEmits {
+ 'update:modelValue': [value: string | number]
+}
type SlotProps = (props: { item: T, index: number, ui: Tabs['ui'] }) => VNode[]
@@ -90,12 +112,13 @@ export type TabsSlots = {
'content'?: SlotProps
'list-leading'?(props?: {}): VNode[]
'list-trailing'?(props?: {}): VNode[]
+ 'more'?(props: { items: T[], isActive: boolean, ui: Tabs['ui'] }): VNode[]
} & DynamicSlots