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-tags

v1.0.2

Published

Get-Set Tags — accessible tag / token input

Readme

@get-set/gs-tags

A dependency-free, fully accessible (WAI-ARIA) tag / token input available in two flavours from one codebase:

  • Native / vanilla JS — a window.GSTags(selectorOrElement, params) factory that enhances an <input> (also exposed as a jQuery plugin and an HTMLElement.prototype method).
  • React — a <GSTags /> forwardRef component with a typed GSTagsHandle (add/remove/clear/getTags/setTags/focus/blur/ destroy).

Both surfaces drive one shared core (actions/, constants/, helpers/, types/), so behaviour is identical across the two — every option works in both targets (except the few tagged React-only below).

Features

  • Rich delimiter set — commit tags on Enter, ,, ;, space, Tab, blur, or by splitting pasted text (delimiters + pasteSplit).
  • Validation pipelinemaxTags, allowDuplicates, validate (RegExp | string | predicate), allowedPattern (per-keystroke block), restrictToSuggestions, with onInvalid reporting the exact reason.
  • Transformstrim, lowercase, uppercase, capitalize (single or ordered list) applied before a value becomes a tag.
  • Autocompletesuggestions as a static array or an async resolver (debounced), with keyboard navigation, minChars, maxSuggestions and hide-already-selected.
  • Interactions — inline editable tags (double-click), per-tag remove ×, drag-to-reorder, and Backspace-removes-last.
  • Beautiful motion — spring tag pop-in / pop-out, reorder transitions and a dropdown reveal, all honoring prefers-reduced-motion.
  • Accessiblerole="list"/listitem, combobox + listbox/option wiring, aria-expanded/aria-autocomplete, labelled remove buttons and full keyboard support.
  • Light / dark / auto theming + accentColor, three sizes (sm/md/lg), RTL, disabled / readOnly, hidden-input form submission (name), per-part customClass, and a React-only scoped-styles gsx prop.

Compatibility

| Target | Requirement | |---|---| | React (<GSTags>) | React 16.8+ (Hooks + forwardRef), and 17 / 18 / 19. React is an optional peer dependency — you only need it for the component. | | Native / vanilla (window.GSTags) | No framework. Any modern evergreen browser. Optional jQuery ($(...).GSTags(...)) and HTMLElement.prototype.GSTags(...) adapters are registered automatically by the bundle. | | TypeScript | First-class — type declarations (.d.ts) ship in the package. | | SSR / Next.js | Safe to import; render <GSTags> inside a Client Component ('use client'). |

Project layout

GSTags.ts                  # native entry (webpack -> dist-js/bundle.js, window global)
components/GSTags.tsx       # React component (tsc -> dist/, npm entry)
actions/                   # shared engine (init/mutate/render/suggestions/destroy/refresh)
constants/                 # default params + sizes
helpers/                   # tags (validation/transform/split), keys, layout, ui helpers
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)

Install

npm install @get-set/gs-tags

Build & test

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/
npm test               # vitest

See example.html for a runnable native showcase.


Usage — Native JS

<input id="skills" placeholder="Add a skill…" />

<link rel="stylesheet" href="node_modules/@get-set/gs-tags/styles/GSTags.css" />
<script src="node_modules/@get-set/gs-tags/dist-js/bundle.js"></script>
<script>
  // The factory accepts a CSS selector STRING **or** a DOM element.
  const tags = new GSTags('#skills', {
    value: ['react', 'typescript'],
    placeholder: 'Add a skill…',
    maxTags: 8,
    delimiters: ['enter', 'comma', 'paste'],
    transform: 'lowercase',
    suggestions: ['react', 'vue', 'svelte', 'angular', 'solid'],
    onChange: (t) => console.log('tags:', t)
  });

  // Imperative control:
  // tags.add('graphql'); tags.remove('vue'); tags.clear();
  // tags.getTags(); tags.setTags(['a','b']); tags.focus();
</script>

Passing a DOM element (also what the adapters do)

const el = document.querySelector('#skills');
const tags = new GSTags(el, { maxTags: 5 }); // element, not a selector

Instance registry

Every instance is registered under its reference (auto-generated when omitted):

new GSTags('#skills', { reference: 'skills' });
const inst = window.GSTagsConfigue.instance('skills');
inst.add('rust');

Usage — jQuery

Registered automatically by the bundle when jQuery is present:

$('#skills').GSTags({ maxTags: 5, delimiters: ['enter', 'comma'] });

Usage — HTMLElement.prototype

Also registered by the bundle:

document.querySelector('#skills').GSTags({ suggestions: ['a', 'b', 'c'] });

Usage — React

'use client';
import { useRef } from 'react';
import GSTags, { GSTagsHandle } from '@get-set/gs-tags';
import '@get-set/gs-tags/styles/GSTags.css'; // or rely on the auto-injected styles

export default function Example() {
  const ref = useRef<GSTagsHandle>(null);

  return (
    <>
      <GSTags
        ref={ref}
        defaultValue={['react', 'typescript']}
        placeholder="Add a skill…"
        maxTags={8}
        delimiters={['enter', 'comma', 'paste']}
        transform="lowercase"
        suggestions={['react', 'vue', 'svelte', 'angular', 'solid']}
        theme="auto"
        onChange={(tags) => console.log(tags)}
      />
      <button onClick={() => ref.current?.add('graphql')}>Add graphql</button>
      <button onClick={() => ref.current?.clear()}>Clear</button>
    </>
  );
}

Controlled mode

const [tags, setTags] = useState<string[]>([]);
<GSTags value={tags} onChange={setTags} />

Async suggestions

<GSTags
  suggestions={async (query) => {
    const res = await fetch(`/api/tags?q=${encodeURIComponent(query)}`);
    return res.json(); // string[] | { value, label }[]
  }}
  minChars={2}
  suggestionDebounce={250}
/>

Imperative handle — GSTagsHandle

| Method | Signature | Description | |---|---|---| | add | (value: string \| string[]) => void | Add one or more tags (validated). | | remove | (valueOrIndex: string \| number) => void | Remove a tag by value or index. | | clear | () => void | Remove every tag. | | getTags | () => string[] | Read the current tags (a copy). | | setTags | (tags: string[]) => void | Replace the whole set (validated). | | focus | () => void | Focus the text input. | | blur | () => void | Blur the text input. | | destroy | () => void | Unregister + remove scoped gsx styles. |

The native instance exposes the same methods (chainable) plus setDisabled(on), refresh() and destroy():

inst.add('a').add(['b', 'c']).remove('b'); // chainable
inst.getTags(); inst.setTags(['x']); inst.focus();
inst.setDisabled(true); inst.refresh(); inst.destroy();

React-only: gsx scoped styles

<GSTags
  gsx={{
    '.gs-tags-tag': { borderRadius: '999px' },
    '.gs-tags-remove:hover': { background: 'crimson' }
  }}
/>

gsx injects a <style data-key="…"> scoped to that instance and removes it on unmount. It is a React-only convenience and has no native equivalent.


Props

Every option below works in both the native factory and the React component unless tagged (React-only).

| Prop | Type | Default | Description | |---|---|---|---| | value | string[] | [] | Initial tags (native seed). In React, the controlled tag list. | | defaultValue (React-only) | string[] | [] | Uncontrolled initial tags. | | placeholder | string | '' | Placeholder in the text input. | | maxTags | number \| false | false | Maximum number of tags. | | allowDuplicates | boolean | false | Allow the same value more than once. | | delimiters | GSTagsDelimiter[] | ['enter','comma'] | Keys / actions that commit the pending text. | | pasteSplit | RegExp \| string | /[,\n;]+/ | Splits pasted text into multiple tags. | | validate | RegExp \| string \| (value, tags) => boolean | — | Reject a candidate before it becomes a tag. | | allowedPattern | RegExp \| string | — | Characters allowed while typing (per-keystroke block). | | transform | GSTagsTransform \| GSTagsTransform[] | — | Normalise a value before committing. | | editableTags | boolean | false | Double-click a tag to edit inline. | | removable | boolean | true | Render an × remove button per tag. | | draggable | boolean | false | Drag tags to reorder. | | backspaceRemoves | boolean | true | Backspace on an empty field removes the last tag. | | addOnBlur | boolean | false | Commit the pending text when the field blurs. | | suggestions | string[] \| (query) => (string \| {value,label})[] \| Promise<…> | — | Autocomplete source (static or async). | | minChars | number | 0 | Minimum query length before suggesting. | | restrictToSuggestions | boolean | false | Only allow values from the suggestions list. | | maxSuggestions | number | 8 | Cap the number of suggestions shown. | | hideSelectedSuggestions | boolean | true | Hide already-selected values from the dropdown. | | suggestionDebounce | number | 200 | Debounce (ms) for async suggestion queries. | | disabled | boolean | false | Disable all interaction. | | readOnly | boolean | false | Tags visible but not editable. | | autofocus | boolean | false | Focus the input on mount. | | name | string | — | Emit a hidden name[] input per tag for form submission. | | theme | 'light' \| 'dark' \| 'auto' | 'auto' | Visual theme. | | accentColor | string | #2563eb | Accent color (CSS var --gst-accent). | | size | 'sm' \| 'md' \| 'lg' | 'md' | Control size preset. | | rtl | boolean | false | Right-to-left layout. | | reducedMotion | 'auto' \| boolean | 'auto' | Honor prefers-reduced-motion. | | customClass | GSTagsCustomClass | — | Per-part class overrides. | | className | string | — | Extra class on the root. | | gsx (React-only) | NestedCSS | — | Scoped inline styles for this instance. | | reference | string | auto | Registry key (auto-generated when omitted). | | onAdd | (value, tags) => void | — | After a tag is added. | | onRemove | (value, tags) => void | — | After a tag is removed. | | onChange | (tags) => void | — | Whenever the tag set changes. | | onInvalid | (value, reason) => void | — | When a candidate is rejected. |

Catalogs (every enum value)

delimitersGSTagsDelimiter

| Value | Commits on | |---|---| | enter | The Enter key. | | comma | Typing ,. | | space | The space bar. | | semicolon | Typing ;. | | tab | The Tab key (commits, then moves focus). | | blur | Losing focus (same as addOnBlur). | | paste | Splitting pasted text (via pasteSplit). |

transformGSTagsTransform

| Value | Effect | |---|---| | trim | Strip leading/trailing whitespace. | | lowercase | value.toLowerCase(). | | uppercase | value.toUpperCase(). | | capitalize | Upper-case the first letter of each word. |

onInvalid reasons — GSTagsInvalidReason

duplicate · maxTags · pattern (failed allowedPattern) · validate (failed validate / restrictToSuggestions) · empty.

themeGSTagsTheme

light · dark · auto (follows prefers-color-scheme).

sizeGSTagsSize

sm · md · lg.

customClassGSTagsCustomClass

root · tag · tagLabel · remove · input · dropdown · option.

Keyboard

| Key | Action | |---|---| | Enter / , / ; / space / Tab | Commit the pending text (per delimiters). | | Backspace (empty field) | Remove the last tag. | | / | Move through the suggestion dropdown. | | Enter (with active suggestion) | Accept the highlighted suggestion. | | Escape | Close the suggestion dropdown. | | Double-click a tag | Edit it inline (editableTags). |

License

ISC © Get-Set