Overflow
Headless responsive truncation primitive. Children render until the container runs out of width, then overflowing items are hidden and an indicator surfaces the hidden count.
Usage
Overflow is built on the createOverflow composable. Wrap any horizontal list of items with Overflow.Root, register each item via Overflow.Item, and add an Overflow.Indicator to render the +N more affordance when truncation kicks in.
<script setup lang="ts">
import { Overflow } from '@vuetify/v0'
const tags = ['Vue', 'React', 'Svelte', 'Solid', 'Qwik']
</script>
<template>
<Overflow.Root class="flex gap-2 overflow-hidden">
<Overflow.Item v-for="tag in tags" :key="tag" :value="tag">
{{ tag }}
</Overflow.Item>
<Overflow.Indicator v-slot="{ count }">
+{{ count }} more
</Overflow.Indicator>
</Overflow.Root>
</template>Anatomy
<script setup lang="ts">
import { Overflow } from '@vuetify/v0'
</script>
<template>
<Overflow.Root>
<Overflow.Item />
<Overflow.Indicator />
</Overflow.Root>
</template>Examples
Tags
The canonical use case. As the container shrinks, trailing tags hide and an indicator counts how many are missing. The default priority="start" keeps leading items visible — natural for tag chips, filters, and breadcrumb-style lists where the first items are the most relevant.
The container needs overflow: hidden so the natural layout doesn’t push items out of frame before the component can react. The indicator’s slot exposes count; you control its rendering completely.
| File | Role |
|---|---|
basic.vue | Tag row that overflows when the container narrows |
Avatar group
The classic “user roster” use case — a stack of overlapping avatars that collapse into a +N chip when the row gets tight. The data lives in a separate users.ts module to keep the markup focused on the visual composition. The overlap comes from per-avatar marginInlineStart: -8px, which createOverflow picks up automatically through getComputedStyle().marginLeft — the Overflow.Root doesn’t need to set the gap prop because the container has no CSS gap and the visual overlap is already in each item’s measured width.
Because each avatar has the same width, the trailing avatars drop in predictable order — no special configuration needed beyond the default priority="start". The indicator inherits the same circular shape and ring so it visually slots into the stack rather than calling attention to itself.
| File | Role |
|---|---|
users.ts | Sample user data (id, name, initials, hue) |
avatar-group.vue | Overlapping avatar stack with +N indicator |
Popover of hidden items
Overflow.Indicator exposes the array of currently-hidden tickets via the hidden slot prop. Wrap the indicator content in a Popover.Activator and render the hidden values inside Popover.Content to give users access to truncated content without losing the compact display.
This is the same pattern used by GitHub’s repo language list and Linear’s project tag list. The indicator only renders when overflow occurs, so the popover trigger naturally appears and disappears with the available space.
| File | Role |
|---|---|
popover.vue | Indicator wrapping a Popover with hidden items |
End priority
When the latest items matter most — chat reactions, recent activity, message lists — priority="end" flips the behavior: leading items hide first and the indicator naturally renders at the start. Visual order is preserved (DOM order = display order); only visibility flips.
Place the Overflow.Indicator first in source order so it renders to the left of the visible items. One tradeoff to keep in mind: the indicator’s aria-live="polite" region announces before the items it summarizes, so screen-reader users hear “+3 earlier” before the most recent entries. That’s usually correct for the “show me what’s new, but tell me how much I missed” reading model — but if your use case needs the items announced first, prefer priority="start" and place the indicator at the end. For breadcrumb-style “first + last, hide middle” bisecting, reach for Breadcrumbs instead — Overflow is deliberately one-sided.
| File | Role |
|---|---|
priority-end.vue | Recent-messages style with end priority |
Recipes
Disable truncation conditionally
<template>
<Overflow.Root :disabled="showAll">
<Overflow.Item v-for="t in tags" :key="t" :value="t">
{{ t }}
</Overflow.Item>
</Overflow.Root>
<button @click="showAll = !showAll">
{{ showAll ? 'Collapse' : 'Show all' }}
</button>
</template>Pin specific items so they always render
<template>
<Overflow.Root>
<Overflow.Item v-for="item in items" :key="item.id" :disabled="item.pinned">
{{ item.label }}
</Overflow.Item>
</Overflow.Root>
</template>A disabled Item is exempt from capacity math and always renders.
Accessibility
| Concern | Mechanism |
|---|---|
| Hidden items announced to AT | Items receive aria-hidden="true" when off-capacity |
| Indicator announcements | aria-live="polite" on the indicator’s element |
| Container semantics | Overflow.Root defaults to <div>; pass as="ul" and as="li" on Items for list semantics |
FAQ
The indicator’s reserved width isn’t known until the indicator has rendered at least once. On the first overflow event, capacity is computed without the reservation, the indicator renders, its width is measured, and capacity recomputes one tick later. The indicator measures itself with a ResizeObserver so changes to its content (e.g., +1 → +99) keep the reservation accurate without manual intervention. This is the same two-tick settle that Breadcrumbs already accepts.
No — Overflow is one-sided by design. Bisect logic lives in the specialized Breadcrumbs component.
Reading getComputedStyle().gap adds a layout pass on every measurement. The component takes the value as a number to keep capacity computation cheap. Mirror it in your CSS or utility class.
Overflow.Root
Props
Slots
default
OverflowRootSlotPropsOverflow.Indicator
Props
Slots
default
OverflowIndicatorSlotPropsOverflow.Item
Props
Slots
default
OverflowItemSlotProps