Proper UI

CLI tool

The Proper UI CLI copies component source into your project and configures Tailwind, straight from the terminal — no copy-pasting required.

The properui CLI copies component source into an existing project, shadcn-style — you get the .tsx files, not a runtime dependency. It detects your framework, TypeScript setup, and Tailwind version automatically, then writes files and installs dependencies for you. This guide covers every command: init, add, list, search, diff, and login.

Installation

There's nothing to install ahead of time — every command runs through npx (or your package manager's equivalent), which always fetches the latest version:

npx @properui/cli@latest init

Or with your preferred package manager: pnpm dlx properui@latest init, yarn dlx properui@latest init, bunx properui@latest init.

Requires Node 20+.

Init command

init configures an existing project — it does not scaffold a new one, so run it from the directory that already holds your package.json. It reads your tsconfig.json for an existing path alias, detects Next.js App Router vs Pages Router vs Vite vs Remix vs plain React, checks your Tailwind version (v3 projects are stopped with upgrade instructions — Proper UI requires Tailwind v4), and confirms your package manager from the lockfile. It then writes components.json, copies the theme token file (from the registry when reachable, a placeholder otherwise), creates utils/cx.ts, adds the Tailwind @source scan line and theme import to your global stylesheet, and wraps your app entry point (app/layout.tsx, pages/_app.tsx, or main.tsx) in ThemeProvider. For Vite specifically, it also writes the @ alias into both halves Vite needs — a paths entry in tsconfig.app.json/tsconfig.json and a matching resolve.alias in vite.config — since Vite has no built-in tsconfig-paths support and vite build/tsc -b fail on @/... imports without both. It does not install any npm packages itself — add is what installs a component's dependencies.

npx @properui/cli@latest init

Command options

OptionDescription
--nextjsTreat the project as Next.js instead of auto-detecting.
--viteTreat the project as Vite instead of auto-detecting.
--manualWrite the files but leave the app entry point (and, for Vite, the @ alias) alone — print the snippet instead.
--overwriteReplace components.json and any files that already exist.
--registry <source>Registry directory or base URL for this run (see below).
-y, --yesAccept every default; never prompt.
--cwd <path>Run against a directory other than the current one.

Add command

add resolves a component and its registryDependencies recursively, copies the source files into your project, rewrites @/ imports to your configured alias, and installs any missing npm packages.

npx @properui/cli@latest add button

Add multiple components in one call:

npx @properui/cli@latest add button input select

If a component depends on another component internally — badge-groups depends on badges, for example — the CLI pulls in the whole dependency chain, so you never end up with a broken import.

npx @properui/cli@latest add badge-groups

The dependency tree above installs badges and dot-icon alongside badge-groups because those are its registryDependencies.

Install every component at once:

npx @properui/cli@latest add --all

Customize the installation directory:

npx @properui/cli@latest add button --path src/components/ui

Typing a name that doesn't exist gets a suggestion rather than a bare failure — the CLI runs a fuzzy match against the registry index and prints the closest names.

Command options

OptionDescription
--allAdd every component in the registry.
--overwriteReplace files that already exist instead of skipping them — how you pull upstream fixes.
--path <dir>Put component files in this directory instead of the configured alias.
--dry-runPrint what would change without writing anything.
--registry <source>Registry directory or base URL for this run (see below).
-y, --yesSkip the "install these dependencies?" confirmation.

--dry-run before --all is a good habit:

npx @properui/cli@latest add --all --dry-run

List command

list prints the registry’s index — every component and example with its layer and description:

npx @properui/cli@latest list
npx @properui/cli@latest list --layer base
npx @properui/cli@latest list --type component
npx @properui/cli@latest list --json

Command options

OptionDescription
--layer <l>Filter by layer, e.g. base, application, marketing, app-examples, marketing-examples, foundations, shared-assets, hooks, utils, styles.
--type <t>Filter by type: component, example, util, hook, style.
--jsonPrint the raw index rows, for scripting.

Search command

search runs a local fuzzy match over component names, descriptions, and example names — a scored subsequence match, not a network call or an AI model. It rewards contiguous, early, and whole-word matches, so "date picker" still finds date-picker, but it only ranks results that are actually a subsequence of the query; it can't reason about what a component looks like or means.

npx @properui/cli@latest search "pricing"
npx @properui/cli@latest search "empty state" --limit 5

Each line of output shows the matched name, its layer, its title, and how many examples reference it, sorted by score.

Command options

OptionDescription
--limit <n>Maximum results (default 20).
--registry <source>Registry directory or base URL for this run (see below).

Diff command

diff compares the files already in your project against the current registry version, so you can see what you've changed (or what's out of date) before pulling an update:

npx @properui/cli@latest diff              # every installed component
npx @properui/cli@latest diff button       # just one

Requires components.json, so run init first.

Adding example pages

add example installs a complete, functional page — a dashboard, a settings screen, a marketing landing page — along with every component and dependency it uses.

npx @properui/cli@latest add example settings-01
npx @properui/cli@latest add example hero-split-image-01

Once an example is installed, it's your code: modify, delete, or extend it however your project needs. The CLI never re-writes an example file after it's on disk unless you pass --overwrite.

Not sure of the exact name? List them first:

npx @properui/cli@latest list --type example

add example accepts the same options as add (--overwrite, --path, --dry-run, --registry, -y/--yes); there's no separate flag to skip an example's dependencies.

Login command

login stores a registry token at ~/.properui/auth.json. It's only needed for a private registry — the public one is anonymous, and add works without ever logging in.

npx @properui/cli@latest login
npx @properui/cli@latest login --token <token>

Run with no --token in an interactive terminal and it prompts you to paste one (after pointing you at <registry>/account/tokens to create it). The token is reused by every later command on the same machine — you only need to do this once.

Command options

OptionDescription
--token <t>Use this token instead of prompting for one.
--registry <source>Registry directory or base URL to store the token against.
-y, --yesNever prompt — fails with an error if no token is given.

Pointing at another registry

A registry source is either an HTTP(S) base URL or a directory on disk. Both expose the same shape: index.json plus one <name>.json per entry. Every command accepts --registry <source>, and the source is resolved in this order:

  1. --registry <source> on the command line
  2. the REGISTRY_URL environment variable
  3. the registry field in components.json
  4. the built-in default, https://properui.dev/r

The hosted registry at https://properui.dev/r goes live when the docs site deploys. Until then, working from a clone of this repo, build the registry locally and point the CLI at the output directory:

pnpm registry:build
npx @properui/cli@latest add button --registry ./packages/registry/dist
# or: REGISTRY_URL=./packages/registry/dist npx @properui/cli@latest add button

Usage examples

Configure an existing Next.js project and add a handful of base components:

npx @properui/cli@latest init --nextjs
npx @properui/cli@latest add button input select badges avatar

Configure an existing Vite app, then preview what an add --all would do before committing to it:

npx @properui/cli@latest init --vite
npx @properui/cli@latest add --all --dry-run

Search for a layout, install it, and overwrite it later once you've pulled in an update:

npx @properui/cli@latest search "hero split image"
npx @properui/cli@latest add example hero-split-image-01
npx @properui/cli@latest add example hero-split-image-01 --overwrite

Troubleshooting

"No components.json found." Run init first, from the directory that holds your package.json.

Imports point at the wrong place. The alias in components.json no longer matches tsconfig.json. Fix the tsconfig path and re-run init --overwrite.

A component installed but renders unstyled. Your stylesheet is missing the @source line for the directory the components landed in. See Installation.

Debugging. Set PROPERUI_DEBUG=1 to get a full stack trace instead of a single error line.

FAQs