Library / SDK
Choices-js/Choices avatar
Choices-js/Choices

Choices.js review: a vanilla JS select box that replaces jQuery Select2

A vanilla JS customisable select box/text input plugin ⚡️

6,827 stars635 forksJavaScriptMIT

At a glance

What is it?
Choices.js is an MIT-licensed JavaScript plugin that turns a plain select or text input into a searchable, taggable control without jQuery. The code is small and the API is broad.
Who is it for?
Adopt Choices.js when you need a searchable multi-select or tag input on a page that has no jQuery and you want to style it yourself through CSS custom properties and custom templates. Do not adopt it if you need a framework-native component with its own state model.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 15 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • Choices-js/Choices6,827 ★ · +3
Star history
Repository2026-09-232026-11-14
Choices-js/Choices6.8k6.8k

3 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

What Choices.js replaces, and for whom

The README describes Choices.js as "a vanilla, lightweight (~20kb gzipped), configurable select box/text input plugin" and places it next to Select2 and Selectize, with the difference stated plainly: no jQuery dependency. That single sentence defines the audience. If your page already loads jQuery, the argument for switching is weaker, because the plugin you have works and the migration cost is real. If you are building on modern bundlers, ES modules and no jQuery, Choices.js is aimed at you.

The scope is narrower than a full form library. It takes an existing select element or text input and layers behaviour on top: searching, filtering, adding items, removing items, and rendering selected values as tags. It does not manage form state, validation or submission.

Who it is for, concretely: teams shipping plain HTML and JavaScript, or a framework where a native element plus a wrapper is acceptable, who need a multi-select with search and want control over markup and styling. Who it is not for: anyone who wants the component to own application state.

How the plugin builds its DOM and where the search cost sits

The search side is where the package layout gets interesting. The exports map in package.json defines four additional entry points beyond the main one: choices.search-basic, choices.search-prefix, choices.search-kmp and choices.search-none. Each has its own import and require paths, for example ./public/assets/scripts/choices.search-kmp.mjs and ./public/assets/scripts/choices.search-kmp.min.js. Going by the entry names alone: a prefix search, a KMP (Knuth-Morris-Pratt) substring search, a basic variant, and a build with no search at all. The practical reading is that the default bundle carries a general-purpose matcher and you can trade matching behaviour for bundle size by importing a narrower entry point. Verify against your own data before assuming the prefix bundle finds what the default one finds.

The main bundle is served as choices.js for require, choices.mjs for import, with types at ./public/types/src/index.d.ts. Styles ship separately as choices.css and, for Sass users, ./src/styles/choices.scss.

Installing Choices.js and wiring a first select

The README gives npm and Yarn as the package manager routes, and jsDelivr for a CDN. The npm package name is choices.js, not choices.

bash
npm install choices.js

For a bundler-based project, the module entry resolves to choices.mjs, so a plain import works. The README also shows importing the stylesheet from JavaScript, which webpack supports:

javascript
import "choices.js/public/assets/styles/choices.css";

If you prefer Sass, the package exports the source stylesheet, and the README shows the import as:

scss
@import "choices.js/src/styles/choices";

For a no-build page, the README pins a CDN version in its examples and warns that the CDN sometimes lags the latest release, telling you to check the latest release and update the pinned version before using it. The two stylesheets are base.min.css (marked optional) and choices.min.css; the Choices stylesheet link is:

html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css" />

Instantiation is one line. The README shows passing a selector, a DOM reference or a jQuery element, and notes an important change: if the selector matches multiple elements, only the first is used, whereas versions before 8.x.x returned multiple instances.

javascript
const element = document.querySelector('.js-choice');
const choices = new Choices(element);

Passing a second argument replaces the defaults for the keys you name. The README lists the default set, starting with silent, items, choices, renderChoiceLimit, maxItemCount, closeDropdownOnSelect, singleModeForMultiSelect and addChoices. After the constructor runs, the page should show the styled control rather than the browser's native dropdown.

The configuration surface is wide, and that is the trade-off

The defaults block in the README is long. It opens with silent, items, choices, renderChoiceLimit, maxItemCount, closeDropdownOnSelect, singleModeForMultiSelect and addChoices, and each option is documented with its type, its default and the input types it affects (text, select-one, select-multiple).

The width of the option set is also the main cost. Every key is another combination to test against your own markup, and the README presents them as a flat list rather than grouping them by task. There is a callbacks section, an events section, a methods section and a CSS custom properties section in the table of contents, so the intended integration path is: configure at construction, react through events and callbacks, and drive state changes through methods rather than by manipulating the generated DOM yourself.

What the README does not tell you

The CHANGELOG.md file exists at the repository root, so version history is recorded there. The README does note one breaking change from before 8.x.x (multi-element selectors now return a single instance).

Search behaviour is another gap. Four search entry points are exported, and package.json names them without saying what input each suits, so check before choosing.

Finally, this is a client-side widget with no server component. If your option list is large, the filtering happens in the browser.

Choices.js compared with Select2 and Selectize

The README itself names the comparison: Choices.js is "similar to Select2 and Selectize but without the jQuery dependency." That is the honest framing of the difference in approach. Select2 and Selectize were built in the jQuery plugin era and depend on jQuery being present. Choices.js assumes no jQuery and ships as an ES module with a CommonJS fallback, which fits projects already bundling their JavaScript.

The second difference is styling strategy. Choices.js lists flexible styling and custom templates among its features and has a CSS custom properties section in its documentation.

The third difference is the split search bundles. Select2's search behaviour is not something you swap by changing an import path; here the package exports four variants. If bundle size is the deciding factor and your filtering needs are simple, that is a concrete advantage. If you need the default matching semantics and cannot verify what the alternatives do from the documentation, it is a decision you have to make by reading code.

Licence, maintenance and upgrade cost

The licence is MIT, stated in the repository metadata and in the LICENSE file at the root. MIT is permissive: it allows use, modification and redistribution, including in closed-source products, provided the copyright notice and permission notice are retained. That is the general shape of the licence, not legal advice for your situation; if you redistribute the bundled script, keep the notice intact.

The repository is not archived, and the last push was on 2026-10-30. Releases are tagged: v11.2.4 on 2026-08-23, v11.2.3 on 2026-04-30, v11.2.2 on 2026-04-13. The version in package.json matches v11.2.4. Release cadence over that window is a patch roughly every few months, which tells you the project is in maintenance rather than rapid feature development.

Upgrade cost has a specific shape here. The package ships built artifacts in public/assets/scripts and types in public/types, so a version bump can change both the runtime behaviour and the type surface. The README documents one historical breaking change around the 8.x line. The practical approach is to pin the version in package.json and read CHANGELOG.md before bumping, especially across a major version. The repository also carries a browser support file (.browserslistrc), an ESLint config, a Prettier config, a Stylelint config and a Playwright config, so the project has its own lint and end-to-end test setup, but that says nothing about whether your integration will survive the upgrade.

Editorial conclusion

Adopt Choices.js when you need a searchable multi-select or tag input on a page that has no jQuery and you want to style it yourself through CSS custom properties and custom templates. Do not adopt it if you need a framework-native component with its own state model. Before committing, check the version you install against the published release list, confirm which search bundle your build pulls in.

Frequently asked questions

How do I install Choices.js with npm?

The README gives the command npm install choices.js, and the package name is choices.js. Yarn is listed as an alternative with yarn add choices.js.

Does Choices.js require jQuery?

No. The README describes it as a vanilla plugin and explicitly contrasts it with Select2 and Selectize on the point that it has no jQuery dependency. It does show passing a jQuery element to the constructor, but that is optional.

What happens if the Choices.js selector matches more than one element?

Only the first matching element is used. The README states that versions prior to 8.x.x would return multiple Choices instances instead.

Official sources

  1. Choices-js/Choices on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/choices-js-choices.svg)](https://hysenlabs.com/projects/choices-js-choices)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/choices-js-choices.svg)](https://hysenlabs.com/projects/choices-js-choices)

Was this useful?