npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

v-fit-children

v2.0.0

Published

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.

Readme

v-fit-children

Auto-hide overflowing children, emit the hidden ones for "+N more" badges

Note: Watch the usage example video to see the directive in action (temporary link).

A Vue 3 directive that automatically hides child elements that don't fit within a container's width. Ideal for chips, badges, tags, or any inline elements in a tight space.

Features

  • Hides children that overflow the container width
  • Emits a custom event with hidden children count, references, and optional data mapping (for "+N more" indicators)
  • Supports gap / column-gap in parent container
  • Accounts for margins, padding, and borders on both container and children
  • Accounts for content overflow (overflow: visible) via scrollWidth
  • Pin specific children so they are never hidden (keepVisibleEl or data-v-fit-keep)
  • Pass your v-for array via data to receive typed hiddenData and hiddenIndices
  • Responds to container resizes via ResizeObserver
  • Monitors individual child size changes via ResizeObserver
  • Detects child additions/removals via MutationObserver
  • Uses a ghost DOM + IntersectionObserver for accurate overflow detection
  • Batches recalculations with requestAnimationFrame for performance
  • Written in TypeScript — ships with full type declarations

Install

npm install v-fit-children

Vue 3 is a peer dependency — it won't be bundled.

Register the directive

Local (per-component) — recommended:

Import the directive in any <script setup> component. Vue auto-registers it because the variable name starts with v:

<script setup lang="ts">
import { vFitChildren } from "v-fit-children";
</script>

Global (app-wide):

Register once in your entry file so every component can use v-fit-children without importing:

import { createApp } from "vue";
import { vFitChildren } from "v-fit-children";
import App from "./App.vue";

const app = createApp(App);
app.directive("fit-children", vFitChildren);
app.mount("#app");

Quick start

<script setup lang="ts">
import { ref } from "vue";
import { vFitChildren } from "v-fit-children";

const containerRef = ref<HTMLElement>();
const hiddenCount = ref(0);

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

<template>
  <div ref="containerRef">
    <div
      v-fit-children="{ 
        widthRestrictingContainer: containerRef, // Optional: defaults to this element
        offsetNeededInPx: 50, // Optional: defaults to 50
      }"
      @fit-children-updated="onUpdate"
    >
      <span v-for="tag in tags" :key="tag">{{ tag }}</span>
    </div>
    <span v-if="hiddenCount">+{{ hiddenCount }} more</span>
  </div>
</template>

The directive element and the width-restricting container can be the same element or different elements. When they differ, the directive element's own margin, border, and padding are subtracted from the available space.

Options

All options are passed as the directive value:

<div v-fit-children="{ widthRestrictingContainer: containerRef, offsetNeededInPx: 80 }">

| Option | Type | Default | Description | |---|---|---|---| | widthRestrictingContainer | HTMLElement | Directive Element | The element whose width constrains the children. Defaults to the element the directive is on. | | offsetNeededInPx | number | 50 | Reserved space in px (e.g. for a "+N more" badge). Set to 0 if you don't need reserved space. | | gap | number | Computed gap | Manually specify the gap between items in pixels. Useful if gap CSS is not used (e.g. inline-block margins). | | data | unknown[] | — | The same array used in v-for. When provided, the event includes hiddenData with the corresponding data objects for hidden children. | | keepVisibleEl | HTMLElement | — | An element (or descendant of a child) that should never be hidden. Useful for inputs or interactive elements. |

Options are reactive — changing them via the directive value triggers a recalculation.

TypeScript

The package ships with full type declarations. Exported types:

import { vFitChildren } from "v-fit-children";
import type { FitChildrenOptions, FitChildrenEventDetail } from "v-fit-children";

FitChildrenOptions

interface FitChildrenOptions<T = unknown> {
  data?: T[];
  gap?: number;
  keepVisibleEl?: HTMLElement;
  offsetNeededInPx?: number;
  widthRestrictingContainer?: HTMLElement;
}

FitChildrenEventDetail

type FitChildrenEventDetail<T = unknown> = {
  hiddenChildren: HTMLElement[];
  hiddenChildrenCount: number;
  hiddenData?: T[];
  hiddenIndices: number[];
  isOverflowing: boolean;
};

Typing the event handler

Vue's @fit-children-updated handler receives a CustomEvent. You can type it like this:

interface Tag {
  id: number;
  label: string;
}

function onUpdate(e: CustomEvent<FitChildrenEventDetail<Tag>>) {
  console.log(e.detail.hiddenChildrenCount);
  console.log(e.detail.hiddenChildren);   // HTMLElement[]
  console.log(e.detail.hiddenIndices);    // number[]
  console.log(e.detail.hiddenData);       // Tag[] | undefined
  console.log(e.detail.isOverflowing);    // boolean
}

Event

The directive dispatches a fit-children-updated custom event on the directive's element whenever visibility is recalculated.

<div
  v-fit-children="{ widthRestrictingContainer: containerRef }"
  @fit-children-updated="onUpdate"
>

The event's detail contains:

| Property | Type | Description | |---|---|---| | hiddenChildrenCount | number | Number of children that were hidden | | hiddenChildren | HTMLElement[] | Direct references to the hidden DOM elements | | hiddenIndices | number[] | DOM indices of the hidden children | | hiddenData | unknown[] | Data objects for hidden children (only present when data option is provided) | | isOverflowing | boolean | true if any children were hidden, false if all fit |

When all children fit (including the offset), isOverflowing is false and no offset space is reserved — the "+N" badge is unnecessary.

Keeping elements visible

You can prevent specific children from being hidden. This is useful for inputs, buttons, or any interactive element that should always remain accessible.

Option A — via directive value (keepVisibleEl):

Pass a ref to the element (or a descendant of a child) that should stay visible:

<script setup lang="ts">
import { ref } from "vue";
import { vFitChildren } from "v-fit-children";

const containerRef = ref<HTMLElement>();
const inputRef = ref<HTMLElement>();
</script>

<template>
  <div ref="containerRef">
    <div v-fit-children="{ widthRestrictingContainer: containerRef, keepVisibleEl: inputRef }">
      <span v-for="tag in tags" :key="tag">{{ tag }}</span>
      <div class="input-wrapper">
        <input ref="inputRef" />
      </div>
    </div>
  </div>
</template>

The directive walks up from keepVisibleEl to find the matching immediate child. So if inputRef points to a nested <input>, the parent child that contains it stays visible.

Option B — via data attribute (data-v-fit-keep):

Add the data-v-fit-keep attribute directly on the child element — no ref needed:

<div v-fit-children="{ widthRestrictingContainer: containerRef }">
  <span v-for="tag in tags" :key="tag">{{ tag }}</span>
  <div data-v-fit-keep>
    <input />
  </div>
</div>

Both methods can be used together. If a kept element is wider than the available space, it stays visible anyway — better to overflow than to hide an input the user is typing in.

Data mapping

Pass your v-for array via the data option to receive the corresponding data objects for hidden children in the event:

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

interface Tag {
  id: number;
  label: string;
  color: string;
}

const tags = ref<Tag[]>([
  { id: 1, label: "Vue", color: "green" },
  { id: 2, label: "React", color: "blue" },
  { id: 3, label: "Angular", color: "red" },
  { id: 4, label: "Svelte", color: "orange" },
]);
const hiddenTags = ref<Tag[]>([]);

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

<template>
  <div
    v-fit-children="{ data: tags, offsetNeededInPx: 50 }"
    @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>

The data array must map 1:1 with the directive's immediate children. hiddenIndices is always provided regardless of the data option, so you can also map manually if needed.

Inline "+N" badge

To keep the badge inline with the chips (instead of below), wrap both in a flex container and give the directive element flex: 1:

<template>
  <div ref="containerRef" style="display: flex; align-items: center; gap: 8px;">
    <div
      style="flex: 1; overflow: hidden;"
      v-fit-children="{ widthRestrictingContainer: containerRef, offsetNeededInPx: 0 }"
      @fit-children-updated="onUpdate"
    >
      <span v-for="tag in tags" :key="tag">{{ tag }}</span>
    </div>
    <span v-if="hiddenCount">+{{ hiddenCount }}</span>
  </div>
</template>

Set offsetNeededInPx: 0 since the badge lives outside the directive element.

How it works

  1. On mount, the directive sets up ResizeObserver on the container (and any parent elements between the wrapper and container), plus a MutationObserver for child list changes.
  2. When any observer fires, a recalculation is scheduled via requestAnimationFrame (deduplicated — only one pending at a time).
  3. A hidden ghost element (display: flex; overflow: hidden) is appended to document.body. Real children are cloned into it — kept children (keepVisibleEl / data-v-fit-keep) go first with flex-shrink: 0, then the rest in DOM order.
  4. The ghost's width is set to containerWidth - offsetNeededInPx. If all children fit without the offset (smart fit), the full container width is used instead.
  5. An IntersectionObserver (root = ghost, threshold = 1.0) determines which clones are fully visible vs. clipped.
  6. Visibility results are mapped back to the real children: visible clones → show, clipped clones → hide.
  7. A fit-children-updated event is dispatched with hidden children, indices, optional data mapping, and overflow status.
  8. On unmount, all observers are disconnected, the ghost is removed, hidden children are restored, and internal state is cleaned up.

Known limitations

  • The directive hides children using display: none !important. If a child has critical display styles set inline, they will be overridden while hidden.
  • keepVisibleEl accepts a single element. To pin multiple children, use data-v-fit-keep on each. Kept elements are never hidden, so if multiple pinned children exceed the container width, they will overflow.

Browser support

Requires browsers that support ResizeObserver, MutationObserver, and getBoundingClientRect. All modern browsers (Chrome, Firefox, Safari, Edge) are supported.

Changelog

2.0.0

Breaking changes:

  • Removed sortBySize option — children are now hidden purely by overflow in DOM order
  • Removed rowCount option — the directive operates on a single row

New features:

  • Added data option to pass your v-for array and receive typed hiddenData in the event
  • Added hiddenIndices to the event detail — always contains DOM indices of hidden children
  • FitChildrenOptions and FitChildrenEventDetail are now generic (<T = unknown>)

Internal:

  • Rewrote overflow detection to use a ghost DOM + IntersectionObserver instead of manual width calculation
  • Accounts for content overflow (overflow: visible) via scrollWidth
  • Fixed post-unmount ghost DOM leak when a requestAnimationFrame callback was pending
  • Fixed gap option being ignored (now correctly applied to the ghost element)

1.0.1

  • Shortened README subtitle
  • Added directive registration guide (local and global)
  • Fixed incomplete sentence in known limitations

1.0.0

  • Initial release with core features: auto-hide, smart fit, gap support, keepVisibleEl, data-v-fit-keep, ResizeObserver/MutationObserver, and RAF batching

License

MIT