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

react-thaizip

v0.3.9

Published

CLI scaffold tool for adding thaizip React address autocomplete components.

Readme

react-thaizip

npm version npm downloads license

A CLI that scaffolds ready-to-use Thai address React components — powered by thaizip — directly into your project, shadcn/ui-style.

📚 Documentation and live component demos

Using a coding agent? Start with the AI usage guide or provide it the site's llms.txt.

| Thai input | English input (romanization alias) | |---|---| | react-thaizip demo — typing "ลาดพร้าว" in the scaffolded ThaiAddressAutocomplete component | react-thaizip demo — typing "ladprao" and matching the same Thai suggestions |

Quick start

npx react-thaizip init
npx react-thaizip add autocomplete
  • init detects your project structure, package manager, and Tailwind version, then writes a thaizip.config.json used by add.
  • add autocomplete scaffolds the component plus its shared dependencies (lib/utils.ts and hooks/use-thai-address-index.ts). It installs missing packages: thaizip, clsx, and tailwind-merge, plus @base-ui/react for the vanilla style. In supported shadcn/ui projects it uses the project's Base, Radix, or React Aria primitives instead. add address-form-field additionally installs react-hook-form.
import { ThaiAddressAutocomplete } from '@/components/thai-address-autocomplete'

<ThaiAddressAutocomplete
  name="address"
  onValueChange={(address) => console.log(address)}
/>

Prerequisite: your project needs Tailwind CSS (v3 or v4) already installed. init detects it but doesn't install it for you — if none is found, it prints a pointer to the Tailwind install docs and exits.

Components

  • autocomplete (ThaiAddressAutocomplete) — free-text address search. The vanilla variant uses Base UI's Combobox.
  • cascade-select (ThaiAddressCascadeSelect) — province > district > sub-district cascade. The vanilla variant uses Base UI's Select (×3).
  • address-form (ThaiAddressForm) — house number + optional moo/soi/street free text, layered directly on top of ThaiAddressCascadeSelect (embedded by file, not by npm import).
  • address-display (ThaiAddressDisplay) — read-only address renderer; no onValueChange/name/hidden inputs, just formats a ThaiAddressDisplayValue you hand it.
  • address-form-field (ThaiAddressFormField) — react-hook-form Controller wrapper around ThaiAddressCascadeSelect.
npx react-thaizip add                         # interactive multiselect
npx react-thaizip add autocomplete            # or: ThaiAddressAutocomplete
npx react-thaizip add cascade-select          # or: ThaiAddressCascadeSelect
npx react-thaizip add address-form            # or: ThaiAddressForm
npx react-thaizip add address-display         # or: ThaiAddressDisplay
npx react-thaizip add address-form-field      # or: ThaiAddressFormField
npx react-thaizip add autocomplete cascade-select  # multiple at once

Each add also requires thaizip >= 0.7.5 (which includes the cascade/enumeration, bilingual-label, and synchronous index-cache APIs used by the templates) — an older installed version makes add exit without writing files.

init selects a component style from the project's components.json: base-*, radix-*, and aria-* choose the corresponding shadcn/ui variant; legacy default and new-york choose Radix. Other or absent styles use the vanilla Base UI variant. The choice is recorded in thaizip.config.json v4. See the configuration reference for all fields and migration behavior.

autocomplete, cascade-select, and address-form share the same shape:

  • Controlled or uncontrolled value / defaultValue / onValueChange (ResolvedThaiAddress | null — FullThaiAddress | null for address-form)
  • name — renders 4 hidden inputs for plain <form> submission: ${name}-subdistrict, -district, -province, -zipcode (address-form renders 4 more of its own on top of the cascade's 4)
  • locale ('th' default or 'en') and a texts prop to override any label/status message
  • disabled / required / onBlur / onError, plus per-part className slots
  • A forwarded ref

address-display is read-only: it takes value / locale / mode, with no onValueChange, name, or hidden inputs.

address-form-field takes react-hook-form's own control / name / rules instead of value / onValueChange, and renders no hidden inputs — submission goes through react-hook-form's own handleSubmit, which reads the form state directly.

Interactive components load the address index on mount by default. Set indexLoad="visible" on autocomplete, cascade select, address form, or address form field when the field starts below the viewport; loading begins when it enters view. Browsers without IntersectionObserver use mount loading.

ThaiAddressAutocomplete

| Prop | Purpose | |---|---| | value / defaultValue / onValueChange | Controlled or uncontrolled ResolvedThaiAddress \| null selection | | name | When set, renders 4 hidden inputs: ${name}-subdistrict, -district, -province, -zipcode | | locale | 'th' (default) or 'en' — drives suggestion labels and default texts | | texts | Partial<Texts> — override any subset of the default labels/status messages | | indexLoad | 'mount' (default) or 'visible' — when to begin loading the address index | | limit / debounce / threshold | Passed through to the underlying search hook | | disabled / required / onBlur / onError | Standard field wiring; onError fires if the bundled address index fails to load | | className / inputClassName / popupClassName / itemClassName | Class-name slots for the wrapper, input, popup, and each suggestion item | | ref | Forwarded to the underlying <input> |

ThaiAddressCascadeSelect

| Prop | Purpose | |---|---| | value / defaultValue / onValueChange | Controlled or uncontrolled ResolvedThaiAddress \| null selection. Changing a parent select in a way that invalidates a full selection fires onValueChange(null) and resets the downstream selects | | name | When set, renders 4 hidden inputs: ${name}-subdistrict, -district, -province, -zipcode | | locale | 'th' (default) or 'en' — drives option labels and default texts | | texts | Partial<Texts> — override any subset of the default labels/status messages | | indexLoad | 'mount' (default) or 'visible' — when to begin loading the address index | | disabled / required / onBlur / onError / aria-invalid | Standard field wiring; onError fires if the bundled address index fails to load | | className / labelClassName / triggerClassName / popupClassName / itemClassName | Class-name slots for the wrapper, labels, each select trigger, popup, and each option item | | ref | Forwarded to the province select's trigger button |

ThaiAddressForm

| Prop | Purpose | |---|---| | value / defaultValue / onValueChange | Controlled or uncontrolled FullThaiAddress \| null — a ResolvedThaiAddress plus houseNo (required) and optional moo/soi/street. Fires only when houseNo is non-blank and the embedded cascade has a full resolution; otherwise the value is null | | name | When set, forwarded to the embedded ThaiAddressCascadeSelect (its own 4 hidden inputs) plus renders 4 more of its own: ${name}-houseno, -moo, -soi, -street | | locale | 'th' (default) or 'en' — drives this component's own labels and the embedded cascade's | | texts | Partial<ThaiAddressFormTexts> — override the house-number/moo/soi/street labels and placeholders | | cascadeTexts | Partial<Texts> forwarded to the embedded ThaiAddressCascadeSelect's own texts prop | | indexLoad | Forwarded to the embedded cascade ('mount' or 'visible') | | disabled / required / onBlur / onError / aria-invalid | Standard field wiring; onBlur fires on the house-number input specifically; onError fires if the bundled address index fails to load (delegated to the embedded cascade); aria-invalid marks the 4 text inputs and the embedded cascade's triggers invalid | | className / labelClassName / inputClassName / triggerClassName / popupClassName / itemClassName | Class-name slots for the wrapper, labels, the 4 text inputs, and the embedded cascade's own trigger/popup/item slots | | ref | Forwarded to the house-number <input> |

ThaiAddressDisplay

| Prop | Purpose | |---|---| | value | ThaiAddressDisplayValue \| null — a ResolvedThaiAddress plus optional houseNo/moo/soi/street. null renders emptyText | | locale | 'th' (default) or 'en' — drives the locality label and the default emptyText | | mode | 'single-line' (default, street + locality joined with ', ') or 'multi-line' (two <span> rows) | | emptyText | Overrides the default "no address" text shown when value is null | | className | Class name for the root <address> element | | lineClassName | Class name for each row's <span> in 'multi-line' mode | | ref | Forwarded to the root <address> element |

ThaiAddressFormField

| Prop | Purpose | |---|---| | control | react-hook-form's Control object, from useForm() | | name | The field path in your form values, e.g. "address" | | rules | react-hook-form validation rules, e.g. { required: 'Please select an address' } — forwarded to Controller | | locale | 'th' (default) or 'en' — drives the embedded cascade's option labels and default texts | | texts | Partial<Texts> forwarded to the embedded ThaiAddressCascadeSelect's own texts prop | | indexLoad | Forwarded to the embedded cascade ('mount' or 'visible') | | disabled | Forwarded to the embedded cascade's triggers | | className / labelClassName / triggerClassName / popupClassName / itemClassName | Class-name slots for the wrapper, and the embedded cascade's own label/trigger/popup/item slots | | errorClassName | Class name for the role="alert" validation message shown when rules fails |

Customizing labels (texts prop)

Every generated component ships with Thai default labels (locale defaults to 'th'); pass locale="en" to switch to the built-in English set. The optional texts prop overrides any subset of the active set:

<ThaiAddressCascadeSelect
  locale="en"
  texts={{
    provinceLabel: 'Province',
    districtLabel: 'District',
    subdistrictLabel: 'Sub-district',
    zipLabel: 'Postal code',
  }}
/>

texts is Partial<Texts>, so you only need to supply the keys you want to change — anything you omit falls back to the locale's default. Each component exports its own Texts type covering its labels, placeholders, and status messages (e.g. loadingText, errorText).

CLI flags

npx react-thaizip init [--yes]
npx react-thaizip add [component...] [--yes] [--overwrite]
npx react-thaizip --help
npx react-thaizip --version
  • --yes, -y — skip confirmation prompts (init and add)
  • --overwrite — overwrite existing files without prompting (add only), including <libDir>/utils.ts and <hooksDir>/use-thai-address-index.ts, which are otherwise never touched once they exist
  • --help, -h — print usage and the list of available components
  • --version, -v — print the CLI's own version
  • init --help / add --help — print command-scoped usage (add --help includes the component list)

Contributing: deploying the documentation site

Deploy apps/docs as a separate Vercel project. Set its Root Directory to apps/docs and use Node.js 22 or newer. The docs app imports the component templates from templates/react/ts/ directly, and those imports (thaizip, clsx, tailwind-merge, @base-ui/react) resolve against this repo's own node_modules, not just apps/docs/node_modules — apps/docs/vercel.json overrides the Install Command to install both. Keep Vercel's default npm run build command. The docs app owns its own package-lock.json and its build uses Webpack for compatibility with the Fumadocs MDX pipeline.