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

@eav93/vue3-treeselect

v0.7.0

Published

Tree select component with nested options for Vue 3.5+ (TypeScript). A fork of vue-treeselect with fixes and performance improvements for large trees.

Downloads

784

Readme

@eav93/vue3-treeselect

A multi-select component with nested options for Vue 3.5+, written in TypeScript.

This is a fork with fixes. It continues riophae/vue-treeselect (Vue 2) → megafetis/vue3-treeselect → SODA-NL/vue3-treeselect.

Compared to the other Vue 3 ports it is:

  • rewritten with the Composition API and TypeScript (types are included);
  • fixed: async search, focus handling, checkboxes, counts, disabled descendants, slots, keyboard navigation, IME input, appendToBody (now a <Teleport>), a watcher leak in async search and more, see the changelog;
  • much faster on large trees (tens of thousands of nodes): incremental selection state, single-pass search, a flat and progressively rendered menu and optional virtual scrolling;
  • covered by a test suite.

The API is the API of vue-treeselect, so its documentation applies, with the differences listed below.

Installation

npm install @eav93/vue3-treeselect

vue ^3.5 is a peer dependency.

Usage

<template>
  <Treeselect v-model="value" :options="options" :multiple="true" placeholder="Select..." />
</template>

<script setup>
import { ref } from 'vue'
import { Treeselect } from '@eav93/vue3-treeselect'
import '@eav93/vue3-treeselect/dist/vue3-treeselect.css'

const value = ref([])
const options = [
  {
    id: 'fruits',
    label: 'Fruits',
    children: [
      { id: 'apple', label: 'Apple' },
      { id: 'banana', label: 'Banana', isDisabled: true },
    ],
  },
  { id: 'vegetables', label: 'Vegetables' },
]
</script>

The component is also the default export (import Treeselect from '@eav93/vue3-treeselect'). A UMD build is available as dist/vue3-treeselect.umd.js (global Vue3Treeselect).

Differences from vue-treeselect

  • v-model uses modelValue / update:modelValue. All events (update:modelValue, select, deselect, open, close, search-change) get the instance id as the last argument.

  • Slots: option-label ({ node, shouldShowCount, count, labelClassName, countClassName }), value-label ({ node }), before-list, after-list.

  • loadOptions may return a promise; for ASYNC_SEARCH the resolved value is used as the options.

  • Selecting a branch never selects its disabled descendants, at any depth.

  • After a search the first match is highlighted (Enter selects it), and the options can change during a search.

  • Works with SSR (appendToBody included) and <KeepAlive>; slots and props are typed.

  • New props:

    | Prop | Default | Description | | --- | --- | --- | | virtualScroll | false | Render only the options in the visible part of the menu. Rows must have a fixed height. | | optionHeight | measured | Row height in px for virtualScroll. | | searchDebounceDelay | 200 | Debounce delay of the search input in ms. |

  • Methods available on the component ref: openMenu, closeMenu, toggleMenu, toggleExpanded, select, clear, removeLastValue, getValue, getNode, isSelected, focusInput, blurInput, getInput, getMenu, getControl, initialize, loadRootOptions, traverseAllNodesDFS, traverseAllNodesByIndex, traverseDescendantsBFS, traverseDescendantsDFS, and the state forest, menu, trigger, localSearch, selectedNodes, internalValue.

  • The menu is a flat list: .vue-treeselect__list-item rows are indented with .vue-treeselect__indent-level-N classes instead of being nested, and options hidden by a search are not rendered.

Localization

All texts are English by default. Other languages are separate entry points, so only the languages you import end up in your bundle (each one is under 1 kB).

For the whole app (recommended): provide the locale once, every <Treeselect> picks it up.

import { createApp } from 'vue'
import { TREESELECT_LOCALE } from '@eav93/vue3-treeselect'
import { ru } from '@eav93/vue3-treeselect/locales/ru'

createApp(App)
  .provide(TREESELECT_LOCALE, ru)
  .mount('#app')

For one component: the locale prop.

<script setup>
import { de } from '@eav93/vue3-treeselect/locales/de'
</script>

<template>
  <Treeselect :options="options" :locale="de" />
</template>

By language code: register the locales you use, then pass the code. Regional codes (ru-RU) map to the language.

import { registerLocale } from '@eav93/vue3-treeselect'
import { ru } from '@eav93/vue3-treeselect/locales/ru'
import { uk } from '@eav93/vue3-treeselect/locales/uk'

registerLocale('ru', ru)
registerLocale('uk', uk)
<Treeselect :options="options" :locale="currentLanguage" />  <!-- 'ru', 'uk', 'en' -->

Your own texts: pass an object; missing texts stay English. Everything can be overridden, including the texts of an imported locale:

<Treeselect :locale="{ placeholder: 'Pick one', noResultsText: 'Nothing found' }" />
<Treeselect :locale="{ ...ru, placeholder: 'Город' }" />

The text props (placeholder, noResultsText, noOptionsText, noChildrenText, loadingText, searchPromptText, retryText, retryTitle, clearAllText, clearValueText, limitText) take precedence over the locale, so placeholder can still be set per component. Order of precedence: text prop → locale prop → app-wide locale → English.

Available locales (@eav93/vue3-treeselect/locales/<code>): ar, cs, de, es, fr, he, hi, id, it, ja, ko, nl, pl, pt, ro, ru, sv, tr, uk, vi, zh (en is exported from the package root). ar and he are right-to-left: set dir="rtl" on a parent element, the menu follows it (also with appendToBody).

With the UMD build (no bundler) there are no locale files: pass your texts as an object.

Customizing styles

Instead of the compiled CSS you can use the SCSS source and configure it with variables (see the top of sass/style.scss for all of them):

@use "@eav93/vue3-treeselect/sass/style.scss" with (
  $treeselect-control-border-color: #ced4da,
  $treeselect-control-border-color-focus: #86b7fe,
  $treeselect-option-bg-highlight: #f5f6fe,
  $treeselect-checkbox-color-highlight: #26a65b,
  $treeselect-multi-value-item-bg: #26a65b,
  $treeselect-multi-value-item-bg-hover: #2bbb66,
  $treeselect-multi-value-font-color: #fff,
);

The SCSS needs the material-colors and sass-easing packages, which are installed as dependencies. Configuring variables is preferable to overriding the generated rules with CSS: it covers all states (hover, focus, disabled) and doesn't fight selector specificity.

Colors and sizes can also be changed at runtime, without Sass, with CSS custom properties: --vue-treeselect-<name> corresponds to $treeselect-<name> (the SCSS value is the default), e.g.

.vue-treeselect {
  --vue-treeselect-control-border-color: #ced4da;
  --vue-treeselect-checkbox-checked-bg: #26a65b;
  --vue-treeselect-padding: 8px;
}

Set them on an ancestor of both the component and .vue-treeselect__portal-target (e.g. :root or a theme class on body) when appendToBody is used: the menu is then rendered outside of the component. The mark colors are --vue-treeselect-checkbox-mark-color / -disabled, the arrow of a branch is mirrored with --vue-treeselect-arrow-direction: -1 (set automatically for [dir="rtl"]). The indentation of a row is padding + level * narrow-cell-width, computed from the --level custom property of the .vue-treeselect__list-item elements.

Checkbox marks are SVG masks painted with $treeselect-checkbox-icon-color / -disabled, and they can be replaced with any CSS image (only its shape matters). For example, Bootstrap-like checkboxes:

@use "@eav93/vue3-treeselect/sass/style.scss" with (
  $treeselect-checkbox-size: 1em,
  $treeselect-checkbox-border-radius: 0.25em,
  $treeselect-checkbox-color: var(--bs-border-color),
  $treeselect-checkbox-color-highlight: var(--bs-primary),
  $treeselect-checkbox-icon-width: 100%,
  $treeselect-checkbox-icon-height: 100%,
  $treeselect-checkbox-checked-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e"),
  $treeselect-checkbox-indeterminate-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e"),
);

Option rows are rendered in blocks with content-visibility: auto, so the browser skips rendering of blocks outside the menu viewport. It clips content overflowing a block; if a custom option-label slot renders something outside of the row, set $treeselect-option-content-visibility: visible (or --vue-treeselect-option-content-visibility: visible).

Large trees

  • Pass non-reactive options, e.g. shallowRef or markRaw, and replace the array to update them. Reactive options are deep-watched (for compatibility), which costs memory and time on large trees.
  • The menu renders the first screens at once and the rest progressively, without blocking the page. For very large expanded trees or search results use virtualScroll.
  • Rows are rendered in blocks with content-visibility: auto, so scrolling, keyboard navigation and selecting stay at 60 fps even with tens of thousands of rendered rows.
  • npm run bench:browser measures opening a menu with 11k expanded options in Chrome (about 130 ms to the first paint, about 100 ms with virtualScroll); bench/browser/perf.mjs measures scrolling, typing, selecting and more.

Development

Requires Node.js 20.19+ or 22.12+ (Vite 8).

npm install
npm test               # tests (vitest + happy-dom)
npm run type-check
npm run build          # dist/ (ES, CJS, UMD, CSS, type declarations)
npm run bench          # benchmark in happy-dom
npm run bench:browser  # benchmark in Chrome (CHROME_PATH, BASE_REF=<git ref> to compare)

License

MIT. Originally created by Riophae Lee and ported to Vue 3 by megafetis.