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

@get-set/gs-select

v1.0.28

Published

Get-Set Select

Readme

GSSelect

A dependency-free, beautifully animated custom <select> / dropdown enhancer available in two flavours from one codebase:

  • Native / vanilla JS — a window.GSSelect(selector, params) factory.
  • React — a <GSSelect /> component.

Both share the exact same engine (actions/, constants/, helpers/, types/), so behaviour is identical across the two. GSSelect progressively enhances a real native <select> element, so it keeps full form semantics (name, required, validation, FormData) while giving you a fully stylable, animated panel with search, multi-select tags, per-option icons, theming and backdrop layers.

Features

  • Modern, token-based control: explicit surface background, hover border, accent focus ring, rotating chevron, placeholder text, clearable × button, disabled and loading (spinner) states
  • Panel open animations: none, fade, slide, scale, flip — tunable animationDuration and animationEasing
  • Backdrop / overlay catalog rendered behind the open panel: none, dim, blur, glass, gradient, vignette, tint — fine-tuned via backdropOptions (opacity, blur, color, colorTo)
  • glass frosted panel surface (translucent tint + hairline border) and softShadow elevated drop shadow
  • Theming: auto / light / dark — complete palettes built on namespaced --gss-* design tokens scoped to the container (nothing leaks into the host page, and the host page's body is never repainted) plus a single accent CSS color that recolors selection, chips, focus ring and the search underline
  • Three control + option-row sizes: sm, md, lg — chips, search and empty-state scale too
  • Built-in type-to-filter allowSearch with searchPlaceholder (single and multiple mode), custom searchFilter matcher, onSearch callback and emptyText empty state
  • multiple selection with polished chips (always-visible remove ×, touch-friendly), maxSelections cap and Backspace-to-remove
  • dropdownPosition: open down, up, or auto (flips upward when there is no room below) + a zIndex param for stacking contexts
  • Custom option HTML via renderOption, per-option leading icons via data-icon (emoji, text, or inline SVG)
  • Selected-option showCheckmark, custom trigger icon, maxHeight scroll cap, closeAfterSelect, rtl layout
  • Responsive per-breakpoint param overrides — re-evaluated live on window resize
  • Lifecycle hooks (afterInit, afterChange, onOpen, onClose, onSearch) plus the underlying <select>'s native onChange
  • Accessible: combobox / listbox / option roles, aria-expanded / aria-selected / aria-activedescendant, labelled chip-remove buttons, full keyboard support (arrows, Home/End, Enter, Escape, Tab-close, type-ahead)
  • Respects prefers-reduced-motion: reduce — transitions/transforms disable automatically
  • Imperative API (open/close/get/set value/refresh/destroy) in both targets

Compatibility

| Target | Requirement | |---|---| | React (the <GSSelect> component) | React 16.8+ (Hooks are required), and 17 / 18 / 19. React is an optional peer dependency — you only need it for the component. | | Native / vanilla (window.GSSelect) | No framework. Any modern evergreen browser. | | TypeScript | First-class — type declarations (.d.ts) ship in the package. | | SSR / Next.js | Safe to import server-side (no DOM access at module load). The enhancer is browser-only, so render it inside a Client Component ('use client'). |

The peer range is ^16.8.0 || ^17.0 || ^18.0 || ^19.0, with react/react-dom marked optional so the native build has zero peer requirements.

Installation

npm i @get-set/gs-select

Project layout

GSSelect.ts               # native entry  (webpack -> dist-js/bundle.js, window global)
components/GSSelect.tsx    # React component (tsc -> dist/, npm entry)
actions/                  # shared engine (init, draw, open/close, search, change, refresh, destroy, enhancements)
constants/defaultParams.ts # shared defaults
helpers/uihelpers.ts      # shared helpers (GUID, scoped-style injection)
types/                    # Params / Ref / Window augmentation
components/styles/         # SCSS + compiled CSS + CSS-as-TS (runtime injection for React)
styles/                   # SCSS + compiled CSS (for <link> use by the native build)

Build

npm install
npm run build          # builds both targets
npm run build:js       # native bundle -> dist-js/bundle.js
npm run build:react    # React + types  -> dist/

Tests

Unit tests use Vitest + jsdom:

npm test         # run once
npm run test:watch

Usage — Native JS

The markup is a plain native <select> (with <option> / <optgroup> children). Add the stylesheet, then enhance the element by selector:

<link rel="stylesheet" href="styles/GSSelect.css" />

<select class="select1" name="country" required>
  <option value="" hidden>Select country</option>
  <option value="us" data-icon="🇺🇸">United States</option>
  <option value="de" data-icon="🇩🇪">Germany</option>
  <option value="fr">France</option>
</select>

<script src="dist-js/bundle.js"></script>
<script>
  new GSSelect('.select1', {
    animation: 'flip',
    theme: 'dark',
    accent: '#22d3ee',
    glass: true,
    backdrop: 'blur',
    allowSearch: true,
    searchPlaceholder: 'Search…',
  });
</script>

From a CDN:

<script src="https://unpkg.com/@get-set/gs-select/dist-js/bundle.js"></script>
<script>
  new GSSelect('.select1');
</script>

The native bundle also registers a jQuery plugin and an HTMLElement.prototype adapter, so you can enhance elements directly (every form registers into window.GSSelectConfigue):

// jQuery (when jQuery is present on the page)
$('.select1').GSSelect({ allowSearch: true });

// HTMLElement.prototype
document.querySelector('.select1').GSSelect({ multiple: true });

new GSSelect(...) accepts a CSS selector or a <select> element, so the jQuery / prototype adapters (which pass the element) behave exactly like new GSSelect('.select1', params).

Instance methods (registry)

Give the instance a reference key, then look it up via the global registry and call methods on the returned ref:

new GSSelect('.select1', { reference: 'country' });

const inst = window.GSSelectConfigue.instance('country');
inst.refresh();  // rebuild the enhanced select (e.g. after options change)
inst.destroy();  // tear down the enhancement + unregister

window.GSSelectConfigue holds:

  • referencesArray<{ key: string; ref: Ref }> of every live instance.
  • instance(key) — returns the registered Ref for key (or undefined).

The returned Ref exposes select (the underlying <select>), currentParams, reference, refresh() and destroy().

Usage — React

import GSSelect from '@get-set/gs-select';

export default function CountryPicker() {
  return (
    <GSSelect
      animation="scale"
      theme="auto"
      accent="#4f46e5"
      allowSearch
      searchPlaceholder="Search…"
      onChange={(e) => console.log('value', e.target.value)}
    >
      <option value="" hidden>Select country</option>
      <option value="us" data-icon="🇺🇸">United States</option>
      <option value="de" data-icon="🇩🇪">Germany</option>
      <option value="fr">France</option>
    </GSSelect>
  );
}

The component renders a real <select>; pass <option> / <optgroup> as children. Plugin-only props (see table below) are stripped and never leak onto the DOM <select>; all other standard <select> attributes (name, required, disabled, onChange, …) pass straight through. Styles are injected at runtime — no CSS import required.

Next.js (App Router)

The enhancer is browser-only, so use it from a Client Component:

'use client';
import GSSelect from '@get-set/gs-select';

export default function CountryPicker() {
  return (
    <GSSelect theme="dark" glass backdrop="blur">
      <option value="us">United States</option>
      <option value="de">Germany</option>
    </GSSelect>
  );
}

Imperative ref (GSSelectHandle)

The component exposes an imperative handle through its ref (not the raw <select> node):

import GSSelect, { GSSelectHandle } from '@get-set/gs-select';
import { useRef } from 'react';

function App() {
  const select = useRef<GSSelectHandle>(null);

  return (
    <>
      <GSSelect ref={select} multiple>
        <option value="1">One</option>
        <option value="2">Two</option>
      </GSSelect>

      <button onClick={() => select.current?.open()}>Open</button>
      <button onClick={() => select.current?.setValue(['1', '2'])}>Pick both</button>
      <button onClick={() => console.log(select.current?.getValue())}>Read value</button>
    </>
  );
}

| Method | Signature | Description | |---|---|---| | open() | () => void | Open the dropdown panel. | | close() | () => void | Close the dropdown panel. | | getValue() | () => string \| string[] | Current value. Returns a string, or a string[] for multiple selects. | | setValue(value) | (value: string \| string[]) => void | Set the value (string, or string[] for multiple) and re-sync the UI (also fires the native change event). | | refresh() | () => void | Rebuild the enhanced select — call after the option set changes. | | destroy() | () => void | Tear down the enhancement and unregister the instance. | | getElement() | () => HTMLSelectElement \| null | Escape hatch — the underlying native <select> element. |

Scoped styles with gsx (React only)

The React-only gsx prop injects styles scoped to a single instance (matched by its generated data-key). It accepts a nested CSS object (NestedCSS) where nested keys become descendant selectors:

<GSSelect
  gsx={{
    // applies to the root <select> for this instance
    fontSize: '14px',
    // nested selectors target the rendered dropdown parts
    '.gs-select-container': { borderRadius: '10px' },
    '.gs-select-option': { padding: '8px 12px' },
  }}
>
  <option value="1">One</option>
  <option value="2">Two</option>
</GSSelect>

Options

All options work identically in both targets — pass them as React props on <GSSelect> or as the second argument to new GSSelect(selector, params). Params also extends the native SelectHTMLAttributes (minus size), so standard <select> attributes (name, required, disabled, multiple, onChange, className, …) are accepted too.

| Option | Type | Default | Description | |---|---|---|---| | reference | string | random GUID | Unique key for the registry / lookups. | | icon | string | '<icon class="gs-select-default"></icon>' | Trigger icon markup (HTML string, emoji, or text). | | closeAfterSelect | boolean | true (forced to false for multiple) | Close the panel after picking an option. | | allowSearch | boolean | true | Show the type-to-filter search box. | | searchPlaceholder | string | 'Type for searching' | Placeholder text for the search input. | | maxHeight | number | 300 | Max height (px) of the scrollable option list. | | disableStyles | boolean | false | Skip injecting the plugin's default styles (bring your own CSS). | | responsive | Array<{ windowSize, params }> | [] | Per-breakpoint param overrides (applied when window.innerWidth <= windowSize). | | multiple | boolean | false | Multi-select with rendered tags. Also enabled by the native multiple attribute; forces closeAfterSelect: false. | | animation | 'none' \| 'fade' \| 'slide' \| 'scale' \| 'flip' | 'scale' | Panel open animation. | | animationDuration | number (ms) | 220 | Open/close transition duration. | | animationEasing | string | 'cubic-bezier(0.4, 0, 0.2, 1)' | CSS timing function for the open animation. | | glass | boolean | false | Frosted glass panel surface (translucent tint + hairline border). | | softShadow | boolean | false | Softer, elevated drop shadow on the panel. | | backdrop | 'none' \| 'dim' \| 'blur' \| 'glass' \| 'gradient' \| 'vignette' \| 'tint' | 'none' | Overlay layer rendered behind the open panel. | | backdropOptions | { type?, opacity?, blur?, color?, colorTo? } | – | Fine-tune the backdrop. type overrides the top-level backdrop string. See below. | | theme | 'auto' \| 'light' \| 'dark' | 'auto' | Color theme. auto follows prefers-color-scheme. | | accent | string (CSS color) | #4f46e5 (CSS default) | Accent color for selection, focus ring and the search underline. Sets --gs-accent. | | rtl | boolean | false | Right-to-left layout (also sets dir="rtl"). | | size | 'sm' \| 'md' \| 'lg' | 'md' | Control + option-row sizing. | | showCheckmark | boolean | true | Show a checkmark next to the selected option(s). | | emptyText | string | 'No results found' | Empty-state message shown when search has no matches. | | placeholder | string | – | Placeholder shown in the control while nothing is selected (deselects the auto-picked first option on first init; an explicit selected attribute wins). | | clearable | boolean | false | Show a clear (×) button on the control while a value is selected. Clearing fires the normal change pipeline. | | disabled | boolean | – | Disable the control (dimmed, not-allowed cursor, opening blocked). Also honoured from the native disabled attribute. | | loading | boolean | false | Loading state — shows an accent spinner in the control and blocks opening. | | maxSelections | number | 0 (unlimited) | Cap the number of selected options for multiple selects; extra clicks are ignored until a slot frees up. | | dropdownPosition | 'down' \| 'up' \| 'auto' | 'down' | Panel opening direction. auto flips upward when there is no room below the control. | | zIndex | number | 999 (CSS default) | Stacking z-index for the open panel (sets --gs-z; the backdrop sits one level below). | | searchFilter | (query, label, value) => boolean | contains-match | Custom search matcher. Return true to keep the option visible. | | renderOption | (option: HTMLOptionElement) => string | – | Custom option renderer — returns an HTML string for the option label. | | gsx | NestedCSS | – | React only — scoped inline styles for the instance. | | afterInit | (select: HTMLSelectElement) => void | – | Fired after the enhanced select is initialised. | | afterChange | (select: HTMLSelectElement) => void | – | Fired after the selection changes (exactly once per change, in both single and multiple mode). | | onOpen | (select: HTMLSelectElement) => void | – | Fired when the dropdown panel opens. | | onClose | (select: HTMLSelectElement) => void | – | Fired when the dropdown panel closes. | | onSearch | (query: string, select) => void | – | Fired on every search keystroke with the current query. |

backdropOptions

| Field | Type | Description | |---|---|---| | type | GSSelectBackdrop | Backdrop kind. Overrides the top-level backdrop string when both are set. | | opacity | number (0–1) | Overlay opacity when the panel is open (clamped to 0–1). Sets --gs-backdrop-opacity. | | blur | number (px) | Blur radius for blur / glass backdrops. Sets --gs-backdrop-blur. | | color | string (CSS color) | Base color for dim / tint / glass / gradient / vignette. Sets --gs-backdrop-color. | | colorTo | string (CSS color) | Secondary color for the gradient / vignette backdrops. Sets --gs-backdrop-color-to. |

Variant catalogs

Animations (animation)

| Value | Description | |---|---| | none | No transition — panel appears instantly. | | fade | Opacity fade in/out. | | slide | Slide the panel into place. | | scale | Scale/zoom the panel from the trigger (default). | | flip | 3D flip reveal. |

Backdrops (backdrop / backdropOptions.type)

| Value | Description | |---|---| | none | No backdrop layer (the element is removed). | | dim | Solid color overlay that fades to opacity while open. | | blur | Backdrop-filter blur (blur px) behind the panel. | | glass | Frosted overlay — color tint + blur px blur with extra saturation. | | gradient | Radial gradient from color to colorTo. | | vignette | Radial vignette — transparent center (colorTo) darkening to color at the edges. | | tint | Flat color tint at opacity. |

Themes (theme)

| Value | Description | |---|---| | auto | Follows the OS prefers-color-scheme (default). | | light | Force light theme. | | dark | Force dark theme. |

Sizes (size)

| Value | Description | |---|---| | sm | Compact control + option rows. | | md | Default sizing. | | lg | Large control + option rows. |

Per-option leading icons

Add a data-icon attribute to any native <option> (emoji, text, or inline SVG markup) to render a leading icon for that row:

<option value="us" data-icon="🇺🇸">United States</option>
<option value="de" data-icon="🇩🇪">Germany</option>

Examples

Native — themed, glass, with backdrop

<link rel="stylesheet" href="styles/GSSelect.css" />
<select class="picker" name="country" required>
  <option value="" hidden>Select country</option>
  <option value="us" data-icon="🇺🇸">United States</option>
  <option value="de" data-icon="🇩🇪">Germany</option>
  <option value="fr">France</option>
</select>

<script src="dist-js/bundle.js"></script>
<script>
  new GSSelect('.picker', {
    reference: 'country',
    animation: 'flip',
    animationDuration: 260,
    theme: 'dark',
    accent: '#22d3ee',
    size: 'lg',
    glass: true,
    softShadow: true,
    backdrop: 'blur',
    backdropOptions: { opacity: 0.4, blur: 8 },
    allowSearch: true,
    searchPlaceholder: 'Search…',
    emptyText: 'Nothing matched',
    afterChange: (sel) => console.log('value', sel.value),
  });
</script>

React — multiple select with tags + ref

'use client';
import GSSelect, { GSSelectHandle } from '@get-set/gs-select';
import { useRef } from 'react';

export default function Tags() {
  const ref = useRef<GSSelectHandle>(null);

  return (
    <>
      <GSSelect
        ref={ref}
        multiple
        animation="scale"
        theme="auto"
        accent="#4f46e5"
        showCheckmark
        backdrop="gradient"
        backdropOptions={{ color: '#1e1b4b', colorTo: 'transparent', opacity: 0.5 }}
        onChange={(e) => console.log(Array.from(e.target.selectedOptions).map((o) => o.value))}
      >
        <option value="1" data-icon="🍎">Apple</option>
        <option value="2" data-icon="🍌">Banana</option>
        <option value="3" data-icon="🍒">Cherry</option>
      </GSSelect>

      <button onClick={() => ref.current?.setValue(['1', '3'])}>Pick apple + cherry</button>
    </>
  );
}

Theming & design tokens

All colors flow through namespaced CSS custom properties (--gss-*) scoped to .gs-select-container — the stylesheet never touches :root or the host page's body. Forced theme: 'light' / 'dark' redefine the complete token palette, so every surface (control, panel, chips, search inputs, scrollbars, shadows, glass) is correct regardless of the OS color scheme; theme: 'auto' follows prefers-color-scheme.

Key tokens you can override per instance (via gsx, a wrapper class, or inline style):

.my-scope .gs-select-container {
  --gs-accent: #22d3ee;      /* also settable via the accent param */
  --gss-bg: #ffffff;         /* control surface */
  --gss-color: #111827;      /* text */
  --gss-muted: #6b7280;      /* secondary text / chevron */
  --gss-border: #d1d5db;     /* control border */
  --gss-panel-bg: #ffffff;   /* dropdown panel surface */
  --gss-hover: #f3f4f6;      /* option hover/highlight */
  --gss-chip-bg: #eef2ff;    /* multi-select chip */
  --gss-radius: 10px;        /* control radius */
  --gss-panel-radius: 12px;  /* panel radius */
}

Where supported, color-mix() derives the selection tint, focus ring and chip colors from --gs-accent automatically, so a single accent param re-skins the whole widget in every theme.

Accessibility & motion

  • The control is a role="combobox" with aria-haspopup="listbox", aria-expanded and aria-controls wired to the role="listbox" panel (aria-multiselectable for multiple).
  • Every option row is a role="option" with live aria-selected / aria-disabled, and the highlighted row is exposed through aria-activedescendant.
  • Chip remove buttons carry aria-label="Remove <label>"; the search input has an aria-label and aria-autocomplete="list".
  • Keyboard: ArrowUp/ArrowDown move the highlight, Home/End jump to the first/last option, Enter selects (or opens when closed, along with Space/ArrowDown), Escape closes, Tab closes and moves focus on, Backspace removes the last chip, and typing filters (or type-aheads when allowSearch: false).
  • A visible accent focus ring is shown on :focus-visible (no more outline: none black holes).
  • Animations respect prefers-reduced-motion: reduce — transitions, transforms and the loading spinner animation are disabled automatically.

License

ISC.