@ozjsey/v-fit-children

Vue directive that auto-hides children that don't fit within a container's width. Useful for chips, badges, tags, and other inline elements in tight spaces.
1,104
Install
npm install @ozjsey/v-fit-children
Latest Version:2.3.3
Vue:3
License:MIT
Last Updated:Sep 20, 2026
Links: GitHub  ·  npm
Maintainer: ozjsey

@ozjsey/v-fit-children

A Vue 3 directive that hides the children which do not fit a container's width and emits the ones it hid — for chips, badges, tags, or any inline row in a tight space.

npm license MIT gzipped 2.67 KiB dependencies 0

The problem

A row of chips that has to fit is a measurement problem wearing a layout problem's clothes. You need a ResizeObserver on the container — and on the host, its parent, every child and every sibling, because a "+N" badge growing beside a shrink-to-fit row changes none of the first three. You need what each child wants, not the width the row squeezed it into: a flex item with flex-shrink: 1 and overflow: hidden has an automatic minimum size of zero, so its box is your own answer echoed back. And you need the count before the browser paints, or the user watches the row sit clipped.

The solution

Put v-fit-children on the row. Children that do not fit get data-v-fit-hidden, the host gets data-v-fit-state="fits" | "overflowing" for CSS-only styling, and a fit-children-updated event carries what you lost: the count, the elements, their DOM indices, and — when you pass your v-for array as data — the matching data objects, typed.

<div v-fit-children @fit-children-updated="onUpdate">
  <span v-for="tag in tags" :key="tag">{{ tag }}</span>
</div>

The real children are measured in place, so there are no clones and spacing is whatever the browser actually laid out. Hiding is an attribute plus one injected rule, never style.display, so v-show and <Transition> compose with it.

Two things to know on day one. It assumes a single non-wrapping row; a flex-wrap: wrap host is not supported. And because hiding rides on an injected stylesheet, a style-src CSP without 'unsafe-inline' blocks it silently — no error, children simply overflow. Ship [data-v-fit-hidden] { display: none !important } in your own CSS and the injection is a no-op.

Install

npm install @ozjsey/v-fit-children

Requires Vue 3.0 or newer: the package imports one type (Directive) from Vue and nothing at runtime, so its floor is the first version that had directives at all.

Register it per component — import { vFitChildren } from '@ozjsey/v-fit-children' in <script setup>, which Vue picks up because the name starts with v — or app-wide with app.directive('fit-children', vFitChildren).

Usage

A row, and a "+N more" badge

<script setup lang="ts">
import { ref } from 'vue'
import { vFitChildren, type FitChildrenEventDetail } from '@ozjsey/v-fit-children'

const tags = ref(['Vue', 'TypeScript', 'Vite', 'Vitest', 'Nuxt', 'Pinia'])
const hiddenCount = ref(0)

function onUpdate(e: CustomEvent<FitChildrenEventDetail>) {
  hiddenCount.value = e.detail.hiddenChildrenCount
}
</script>

<template>
  <div v-fit-children @fit-children-updated="onUpdate">
    <span v-for="tag in tags" :key="tag">{{ tag }}</span>
  </div>
  <span v-if="hiddenCount">+{{ hiddenCount }} more</span>
</template>

offsetNeededInPx reserves room for that badge and defaults to 50, but only once the row genuinely overflows — a badge you do not need never costs you width. Set it to 0 when the badge lives outside the host. Read hiddenChildrenCount, not isOverflowing: a fully pinned row can be visibly clipped with nothing hidden, where v-if="isOverflowing" renders +0 more.

Map the hidden children back to your data

<script setup lang="ts">
import { ref } from 'vue'
import { vFitChildren, type FitChildrenEventDetail } from '@ozjsey/v-fit-children'

interface Tag { id: number; label: string }

const tags = ref<Tag[]>([{ id: 1, label: 'Vue' }, { id: 2, label: 'React' }, { id: 3, label: 'Svelte' }])
const hiddenTags = ref<Tag[]>([])

function onUpdate(e: CustomEvent<FitChildrenEventDetail<Tag>>) {
  hiddenTags.value = e.detail.hiddenData ?? []
}
</script>

<template>
  <div v-fit-children="{ data: tags }" @fit-children-updated="onUpdate">
    <span v-for="tag in tags" :key="tag.id">{{ tag.label }}</span>
  </div>
  <select v-if="hiddenTags.length">
    <option v-for="tag in hiddenTags" :key="tag.id">{{ tag.label }}</option>
  </select>
</template>

data must map 1:1 with the host's immediate children — mark anything that is not one of your items data-v-fit-decorative, and the directive warns once when the two disagree. Use hiddenData for your items; hiddenIndices holds DOM positions, a different index space as soon as the row contains a decorative child. A v-show child does not skew it — it consumes its data index like any other.

Pin the children that must never disappear

<template>
  <div v-fit-children="{ data: tags }">
    <span v-for="tag in tags" :key="tag.id">{{ tag.label }}</span>
    <div data-v-fit-keep data-v-fit-decorative>
      <input placeholder="add a tag" />
    </div>
  </div>
</template>

data-v-fit-keep survives the cull; data-v-fit-decorative keeps that wrapper out of the data mapping, and a pinned control needs both or the mapping skews for every item after it. If the pinned children alone exceed the width they overflow rather than vanish — the honest failure for something the user is typing in.

Everything else

Every option, attribute and event field — widthRestrictingContainer, gap, keepVisibleEl, isOverflowing, the dispatch contract, what happens to v-show children — is driven with a live width slider on the v-fit-children playground tab, one card per feature, every card editable in place: chips with a +N badge · data mapping · pinned children · inline badge · the state attribute · decorative children · the event contract

CHANGELOG.md · ARCHITECTURE.md

License

MIT

Related Packages

text-truncate-scroll

A light-weight js utility that truncates overflowing text with ellipsis, and sho...

11,369 15
@sparing-software/v-clamp

Vue directive plugin for text truncate

3,655 15
vue-resize-text

Vue Resize Text makes font auto resizing on responsive layout

30,606 69
vue-element-queries

A Vue directive for using element queries to build responsive components.

697 34
@ozjsey/v-teleport-to

Vue 3 directive for viewport-aware fixed positioning relative to a reference ele...

32 0

Version History

Version Released Vue License
2.3.3 ^3.0.0 MIT
2.3.2 ^3.0.0 MIT
2.3.1 ^3.0.0 MIT
2.3.0 ^3.0.0 MIT
2.2.0 ^3.0.0 MIT