shadcn-ui/ui
github.com/shadcn-ui/ui ↗ · 3999 files · 14.7 MB · scan capped
shadcn/ui A set of beautifully designed components that you can customize, extend, and build on.
Overview
shadcn/ui is a pnpm+turbo monorepo centered on the `shadcn` CLI (packages/shadcn, v4.21.0), which scaffolds projects and copies registry-hosted component source files directly into a user's codebase, transforming imports/CSS vars/RSC markers per project config. apps/v4 is ui.shadcn.com — a Next.js 16 + fumadocs site that authors the component registry (registry/bases, registry/styles) and publishes it as static JSON under public/r, which the CLI fetches by default from https://ui.shadcn.com/r. Supporting published packages are @shadcn/react (unstyled primitives) and @shadcn/helpers (AI/chat utilities); the CLI also embeds a stdio MCP server and ships agent skills under skills/.
System map
The repo is a pnpm 10 + turbo monorepo. The shippable core is packages/shadcn (the `shadcn` CLI, commander-based, bundled to dist/ by tsup). apps/v4 is the Next.js site that both documents components and produces the installable registry JSON consumed by the CLI. packages/react and packages/helpers are small published support libraries. templates/* are standalone starter apps (outside the pnpm workspace) used by `shadcn init`. skills/ holds agent-facing SKILL.md bundles. CI is GitHub Actions; releases go through changesets plus npm publish scripts.
- Monorepo orchestration: root package.json scripts delegate to turbo (build/lint/typecheck/format) and pnpm filters; pnpm-workspace.yaml scopes workspaces to apps/* and packages/* — templates/* are deliberately NOT workspace packages and are synced via scripts/sync-templates.sh.
- CLI entrypoint: packages/shadcn/src/index.ts creates a commander program named 'shadcn' and registers 13 subcommands (init/create, apply, add, diff, docs, view, search, migrate, eject, info, build, mcp, preset, registry) then calls program.parse().
- The CLI ships as the `shadcn` npm package with bin=dist/index.js, built by tsup (ESM, minified, dts) with subpath exports ./registry, ./schema, ./mcp, ./utils, ./icons, ./preset, and ./tailwind.css (copied post-build via tsup onSuccess).
- apps/v4 is the ui.shadcn.com site: Next.js 16.3.3 with fumadocs docs, an app router tree under app/(app) (docs, blocks, charts, examples, create flow) plus dynamic route handlers (app/r/registries.json/route.ts, app/(app)/llm/[[...slug]]/route.ts, app/api/search/route.ts).
- The site doubles as the default registry server: `pnpm registry:build` builds @shadcn/react, @shadcn/helpers, and shadcn, then runs scripts/build-registry.mts, which exports installable item JSON into apps/v4/public/r (871 generated files are committed to git).
- The CLI's default registry endpoint is https://ui.shadcn.com/r (REGISTRY_URL env override) and the built-in '@shadcn' namespace maps to `${REGISTRY_URL}/styles/{style}/{name}.json`.
- @shadcn/react publishes unstyled React primitives (message-scroller, questionnaire, use-render) with both node and browser vitest configs; @shadcn/helpers publishes AI chat/stream helpers split into core, ai-sdk, and tanstack-ai modules.
- Project scaffolding uses a template registry (packages/shadcn/src/templates/index.ts) mapping keys next/vite/start/react-router/astro/laravel to createTemplate configs whose templateDir resolves under SHADCN_TEMPLATE_DIR (dev) pointing at templates/*-app / *-monorepo dirs.
- An MCP server is embedded in the CLI: `shadcn mcp` loads env files then connects a @modelcontextprotocol/sdk Server over StdioServerTransport; `shadcn mcp init` writes client config for claude, cursor, vscode, codex, and opencode.
- Test orchestration is a vitest workspace joining the root config (unit tests, fixtures/templates excluded) with packages/tests (e2e). The root `pnpm test` first builds the v4 registry and starts the site on :4000 via start-server-and-test.
- Releases are driven by changesets (.changeset/config.json ignores v4 and tests) and .github/workflows/release.yml, which publishes the shadcn package to npm on main pushes and on PRs labeled 'release: beta'/'release: rc' (pub:beta/pub:rc/pub:next/pub:release scripts).
- skills/ ships agent-facing skill bundles: skills/shadcn (SKILL.md + cli.md/mcp.md/registry.md + rules/ + evals/) and skills/migrate-radix-to-base (mapping docs) — consumed by AI coding tools, not by the CLI at runtime.
Feature catalog
User-facing surface is dominated by the CLI's subcommands plus the website's create/registry endpoints. The CLI covers project init/scaffolding, component add/search/view/docs, migrations, registry authoring (build/validate/add), presets, dry-run/diff, an MCP server for AI clients, and eject. The site adds docs pages, a /init endpoint returning registry:base items for the create flow, an LLM docs feed, registry directory, and fuzzy search API.
- `shadcn init` (alias `create`): scaffolds new projects from templates or initializes existing ones; flags include --template, --base (base/radix/aria), --monorepo, --preset, --defaults, --rtl/--no-rtl, --pointer, --reinstall, --css-variables; resolves presets to /init URLs and merges a registry:base item into config.
- `shadcn add`: installs registry items addressed by name, URL, local file, github: address, or @namespace/item; supports --all, --overwrite, --path, --dry-run, --diff [path], --view [path]; falls back to an interactive multiselect over the registry index filtered to registry:ui entries.
- `shadcn build`: compiles a registry.json (default ./registry.json) into per-item JSON files under an output dir (default ./public/r), supporting include-resolution via readRegistryWithIncludes and writing a catalog registry.json alongside items.
- `shadcn search` performs fuzzy search across configured registries using fuzzysort over item names/descriptions with type filtering and pagination (SEARCH_CONCURRENCY=8).
- `shadcn view` prints item details and `shadcn docs` returns docs/API/usage examples for components; `shadcn info` reports project config (used by the agent skill via `info --json`).
- `shadcn migrate` runs codemods living in src/migrations: migrate-radix (Radix→Base UI), migrate-icons, migrate-base-color, migrate-rtl, migrate-cn.
- `shadcn apply` applies a preset to an existing project; `shadcn preset` groups decode/url/open/resolve subcommands for preset codes; `shadcn eject` inlines shadcn/tailwind.css and removes the dependency; `shadcn diff` is deprecated in favor of `add --diff`.
- `shadcn registry` group currently exposes `registry add` (add registries to components.json) and `registry validate` (validate a shadcn registry); deprecated/unused registry:mcp and registry build modules exist under commands/registry but are not registered.
- The MCP server exposes 7 tools: get_project_registries, list_items_in_registries, search_items_in_registries, view_items_in_registries, get_item_examples_from_registries, get_add_command_for_items, get_audit_checklist — all reading the project's components.json via getMcpConfig.
- Site create flow: GET /init returns a registry:base item JSON built from query params (theme/font/base/radius) via buildRegistryBase/parseDesignSystemConfig; /init/md returns markdown setup instructions; /init/v0 is a separate v0 variant; ?track=1 fires a Vercel analytics 'create_app' event.
- Site registry surface: /r/registries.json route serves the registries index; public/r/** serves per-item JSON; /llm/[[...slug]] emits LLM-friendly MDX (base detection for base/radix/aria styles); /api/search is fumadocs createFromSource.
- Registry item taxonomy: registryItemTypeSchema enumerates types (registry:ui, block, component, lib, hook, theme, style, base, font, file, item, example, internal, page, etc.) driving install behavior (e.g. registry:theme/style/base skip style-index install and trigger CSS-var overwrite prompts).
Data flow
Input boundaries are CLI args, components.json (cosmiconfig), env files (.env*), and registry item JSON fetched over HTTP(S)/local disk/GitHub. Zod schemas validate all external data. Resolution turns item addresses into URLs via per-registry templates with {name}/{style} placeholders and ${ENV} expansion; resolveRegistryTree recursively merges items+dependencies into one install tree. Updaters then write package.json deps, tailwind config, env files, fonts, transformed source files, and finally CSS. The website's build-registry.mts is the upstream producer of the JSON the CLI consumes.
- Config input: a cosmiconfig explorer (searchPlaces: ['components.json']) loads raw config, rawConfigSchema validates, then resolveConfigPaths resolves tsconfig/jsconfig path aliases (components/ui/lib/hooks/utils) into resolvedPaths — tsconfig load failure throws.
- Item address resolution order in fetchRegistryItems: github: scheme → fetchGitHubRegistryItem; local file → fetchRegistryLocal (with ~ expansion, zod parse); URL → fetchRegistry+parse; '@ns/item' → resolveRegistryItemsFromRegistries; bare name → `styles/{config.style ?? 'new-york-v4'}/{name}.json` under REGISTRY_URL.
- Registry URL building substitutes {name} and {style} placeholders, expands ${ENV_VAR} via expandEnvVars (from AsyncLocalStorage registry env context), appends configured query params, and builds auth headers that are dropped if expansion leaves them empty.
- HTTP fetch path: fetchRegistry → resolveRegistryUrl (v0 /chat/b/ URLs get /json suffix; relative paths prefixed with REGISTRY_URL) → fetchWithProxy (manual redirect loop, SOCKS via ALL_PROXY, EnvHttpProxyAgent for HTTP(S)_PROXY, per-origin header re-scoping) → status-mapped errors (401/403/404/410) → response.json().
- resolveRegistryTree recursively resolves registryDependencies (re-rewriting @namespaced deps through the registries config, erroring RegistryNotConfiguredError when unset) and merges each item's files/cssVars/css/envVars/dependencies/devDependencies/fonts into a single install tree validated by registryResolvedItemsTreeSchema.
- Install pipeline: addComponents → getWorkspaceConfig decides monorepo path (addWorkspaceComponents routes files to ui/hooks/lib workspace targets) vs addProjectComponents; both run resolveAndValidateRegistryTree → massageTreeForFonts → updateDependencies → updateTailwindConfig → updateEnvVars → updateFonts → updateFiles → updateCss (CSS written last so watchers rebuild after files land).
- File writing: updateFiles resolves each file's target path (alias-aware, src-dir aware, --path override), skips identical content via isContentSame, merges .env files instead of overwriting, and runs the transformer chain: transformImport, transformRsc, transformCssVars, transformTwPrefixes, transformIcons, transformMenu, transformAsChild, transformRtl, conditional transformNext (Next 16 middleware), transformFont, transformCleanup.
- Env input boundary: loadEnvFiles loads .env.local, .env.development.local, .env.development, .env from cwd via @dotenvx (no overload) so private-registry ${ENV} placeholders in URLs/headers/params resolve at fetch time.
- components.json write path (init): existing config (or backup) is deepmerged with new config; discovered registries are appended (built-ins filtered out); registry:base config merged last; design settings (menuColor/menuAccent/rtl/iconLibrary) are propagated into each workspace package's components.json; file written with 2-space JSON.
- Registry production: authored sources live in apps/v4/registry/bases/{base,radix,aria} plus registry/styles/style-*.css; build-registry.mts builds STYLE_COMBINATIONS (base × style), invokes the shadcn CLI to export public/r/styles/<style>/*.json, emits ui-rtl variants for nova styles, and formats outputs.
- Dependency writes: updateDependencies resolves the package manager (npm/pnpm/yarn/bun/deno/expo special-cased) and execa's an install of registry-declared dependencies/devDependencies; React 19 + npm triggers a peer-deps warning pointing at ui.shadcn.com/react-19.
- In-process caching: fetchRegistry dedupes in-flight fetches in a module-level Map keyed by url + sha256 of sorted headers; registry headers themselves live in AsyncLocalStorage context set by setRegistryHeaders and cleared via clearRegistryContext in each command's finally.
Behavior traces
Traced flows: CLI startup, `init` (including empty-project scaffold and registry:base merge), `add` (including missing-config fallback into runInit and dry-run), the install pipeline inside addComponents, the registry fetch pipeline, `build`, the MCP server, and the website's registry:build production path.
- Startup: node dist/index.js → imports 13 command modules → new Command().name('shadcn').version(pkg.version) → addCommand(init, apply, add, diff, docs, view, search, migrate, eject, info, build, mcp, preset, registry) → program.parse() → per-command .action handler; SIGINT/SIGTERM exit 0.
- init flow: initOptionsSchema.parse → validate --template/--preset → monorepo-root guard → existing components.json overwrite prompt + optional --reinstall component enumeration → template/base/preset prompts → resolveInitUrl → loadEnvFiles → createFileBackup(components.json) → resolveRegistryBaseConfig(first URL) → runInit.
- runInit: resolveTemplate(template,{monorepo}) → (skip preflight for monorepo template init) preFlightInit → on MISSING_DIR_OR_EMPTY_PROJECT createProject() → getProjectConfig → template.init(...) early-return for template-managed init, else promptForMinimalConfig/promptForConfig → confirm write → ensureRegistriesInConfig → write components.json → getWorkspaceConfig propagation → explorer.clearCaches() → addComponents(['index', url..., 'button']) → templatePostInit.
- add flow: addOptionsSchema.parse → loadEnvFiles → getConfig (createConfig fallback) → ensureRegistriesInConfig → getProjectInfo → warnForDeprecatedComponents → getRegistryItems([first]) to learn item type (theme/style/base skip index, universal items short-circuit to addComponents) → promptForRegistryComponents if none → preFlightAdd → on MISSING_CONFIG prompt then runInit(...) → on MISSING_DIR createProject+runInit → dryRunComponents+formatDryRunResult if --dry-run/--diff/--view → addComponents → updateAppIndex for v0 /chat/b/ blocks.
- Install internals: addComponents → getWorkspaceConfig → addWorkspaceComponents (files grouped by TargetAliasKey ui/hooks/lib, per-target updateFiles) or addProjectComponents → resolveAndValidateRegistryTree → validateFilesTarget → massageTreeForFonts → getSupportedFontMarkers → updateDependencies → updateTailwindConfig → updateEnvVars → updateFonts → updateFiles → resolveOverwriteCssVars → updateCss → log tree.docs.
- Fetch trace: resolveRegistryItemsFromRegistries (URL+headers per item, setRegistryHeaders) → fetchRegistryItems → per-item dispatch → fetchRegistry → resolveRegistryUrl → getRegistryHeadersFromContext → fetchWithProxy manual redirect loop → non-ok maps to RegistryUnauthorized/NotFound/Gone/Forbidden/FetchError with RFC-7807 detail parsing → registryItemSchema.parse → RegistryParseError on failure.
- build trace: preFlightBuild(resolvePaths) → readRegistryWithIncludes(registryFile) → createRegistryCatalog → per item createRegistryItem → mkdir+write outputDir/<name>.json (nested names OK) → write/copy registry.json to outputDir.
- mcp serve trace: shadcn mcp → loadEnvFiles(cwd) → new StdioServerTransport → server.connect → ListToolsRequestSchema returns 7 tools → CallToolRequestSchema → withRegistryContext(handleCallTool, {onGitHubAuthNotice}) → per-tool zod arg parse → getMcpConfig(process.cwd()) → searchRegistries/getRegistryItems → text content responses; errors return isError content blocks.
- Site registry:build trace: pnpm registry:build → build @shadcn/react → @shadcn/helpers → shadcn → bun scripts/build-registry.mts → STYLE_COMBINATIONS from BASES×STYLES → writes registry/bases/__index__.tsx, temp registry/<base-style> trees, registry/__index__.tsx, examples/__index__.tsx → exports public/r/* via the shadcn CLI → copies compiled ui/* into styles/<style>/ui (+ui-rtl for nova) → prettier → cleanup.
- Dry-run trace: --dry-run/--diff/--view short-circuit before writes — ensureRegistriesInConfig with writeFile:false → dryRunComponents(components, config, {overwrite}) runs the same resolve+transform machinery in memory → formatDryRunResult prints per-file diff or content.
Testing signals
Vitest is the single framework. packages/shadcn has dense colocated unit tests plus fixture dirs; packages/tests is a heavier e2e layer that executes the built CLI against copied project fixtures and a stub registry server; @shadcn/react adds browser-mode tests. apps/v4 has only a handful of tests and no coverage config exists anywhere. The root `pnpm test` gate requires building the registry and running the v4 dev server on :4000.
- Root vitest workspace = ./vitest.config.ts (excludes node_modules/fixtures/templates/packages/tests) + ./packages/tests/vitest.config.ts; vite-tsconfig-paths plugin resolves the '@/src/*' alias used throughout packages/shadcn.
- packages/shadcn unit tests are colocated *.test.ts across commands/, registry/, utils/, migrations/, preflights/, preset/ (e.g. resolver.test.ts 2588 LOC, update-files.test.ts 2634 LOC) with 8s testTimeout; fixtures live in test/fixtures (config-*, frameworks/*, project-* variants).
- HTTP mocking uses msw (devDep); proxy.ts deliberately keeps globalThis.fetch because MSW's Node adapter patches it — importing undici fetch would bypass interception and break registry tests (documented in-code).
- packages/tests e2e: vitest globals, 120s timeouts, maxConcurrency 4, globalSetup src/utils/setup.ts; helpers copy fixtures (next-app, vite-app, remix-app, vite-monorepo-imports, registry, etc.) to a temp dir and run `node packages/shadcn/dist/index.js` via execa with CI=true.
- e2e registry isolation: src/utils/registry.ts spins a real http server (default port 4444) serving fake /r endpoints incl. registries.json; REGISTRY_URL env (default http://localhost:4000/r) points tests at the v4 dev server for the full suite.
- e2e coverage: packages/tests/src/tests covers init, add, apply, view, search, registries, typeset — asserting written components.json shape, lib/utils.ts content, package.json deps, and globals.css variables.
- CI test.yml runs three jobs per PR: `pnpm test` (root — which itself spins up v4 on :4000), `pnpm --filter=@shadcn/react test`, `pnpm --filter=@shadcn/helpers test`, all on Node 22 + pnpm 10.33.4; browser-tests.yml additionally runs @shadcn/react browser tests only when packages/react changes.
- @shadcn/react has dedicated browser tests (vitest.browser.config.ts + vitest.browser.setup.ts) including a perf browser test for message-scroller; @shadcn/helpers has colocated unit tests for core/ai-sdk modules.
- apps/v4 is sparsely tested: only a few *.test.ts exist (registry/calendar.test.ts, registry/config.test.ts, app/r/registries.json/route.test.ts); the Next.js UI, create-flow routes, and build-registry.mts pipeline have no direct tests — correctness relies on registry:health/monitor CI jobs.
- Registry integrity is CI-gated: validate-registries.yml runs scripts/validate-registries.mts (and blocks reserved namespaces) when apps/v4/registry/directory.json changes; monitor-registries.yml runs phased health prepare/check/publish scripts.
- No coverage tooling, snapshots exist only for transformers/updaters (__snapshots__ dirs), and there are no tests for the MCP tools layer (mcp/utils.test.ts exists but server handlers are untested) — likely gap areas. (inferred)
Risk map
The dominant risk is supply-chain-style: remote registry JSON drives arbitrary file writes, env-file edits, and package installs inside user projects. Mitigations exist (isSafeTarget path validation, per-origin header scoping on redirects, typed errors, header-safe token checks) but content itself is trusted. Secondary risks: a huge init.ts action with process.exit calls, dead command modules, committed generated registry output, canary deps in the site, and an always-`latest` MCP install instruction.
- Registry items are a remote-code trust boundary: fetched file.content is written into the user's project after validateFilesTarget checks each target/path with isSafeTarget (null bytes, iterative URL-decode traversal, drive letters, absolute-path containment inside cwd) — paths are guarded, file contents are not.
- Private-registry secrets ride in headers/params expanded from ${ENV}; fetchWithProxy follows up to 5 redirects manually and re-attaches caller headers only for same-origin hops (Accept/User-Agent survive cross-origin) — deliberate leak prevention that must be preserved.
- Shell-out surface: updateDependencies execa's package-manager installs of registry-declared deps; `mcp init` runs `<pm> add -D shadcn@latest`; github-cli transport execa's `gh api` with 15s timeout — item-controlled dependency names reach the package manager.
- GitHub item fetching supports GH_TOKEN/GITHUB_TOKEN tokens or the `gh` CLI; tokens are validated against a printable-ASCII pattern before use and GitHubTransportError carries only sanitized fields — raw subprocess output is intentionally never attached.
- init.ts is a 1049-line commander action with ~10 process.exit calls inside prompts; components.json is backed up via FILE_BACKUP_SUFFIX and restored through a process 'exit' listener — fragile control flow where early exits must not leak backups or half-written config.
- Dead command modules: packages/shadcn/src/commands/registry/build.ts and commands/registry/mcp.ts are exported but never registered — the `registry` command only adds `add` and `validate`; stale duplicates of live functionality (build lives at commands/build.ts).
- Generated output is committed: 871 files under apps/v4/public/r are tracked in git while also being build-registry.mts 'persistent outputs' — drift between authored registry sources and committed artifacts is possible if build is skipped before commit.
- Registry items can merge env vars into user .env files (updateEnvVars + env-file merge logic in update-files) — a malicious registry could inject or overwrite environment config.
- apps/v4 pins canary/prerelease deps (@ai-sdk/* canary builds, ai: 'canary', @tanstack/ai betas) — the site/registry build is exposed to upstream instability.
- MCP install instructions hardcode `shadcn@latest` (SHADCN_MCP_VERSION='latest') and tell clients to run `npx shadcn@latest mcp` — no version pinning for the server AI clients will execute.
- Module-level registryCache in fetcher.ts persists for the process lifetime keyed by url+headers hash; in the long-lived MCP server this can serve stale items (mitigated by useCache:false in MCP tool calls). (inferred)
- init propagates design settings into every workspace package's components.json (writes outside the top-level cwd's own config) — a failed run mid-loop could leave inconsistent workspace configs. (inferred)
Extension points
Primary plug-in surfaces: the templates registry + createTemplate factory for new frameworks; the ordered transformer array for content rewrites; the updaters pipeline for new install concerns; registry item types + FILE_TYPE_TO_CONFIG_KEY for new artifact kinds; the components.json registries map for third-party registries; migrations registry for codemods; commander command table and MCP tools array for new CLI/MCP surface; presets and the /init URL contract for create flows; directory.json + validation for the public registry index.
- New framework templates: add a templates/<name>-app (+ -monorepo) dir and a createTemplate entry in src/templates (fields: scaffold/create/init/postInit/files/monorepo overrides, frameworks list for getTemplateForFramework inference).
- Content transforms: add a Transformer module under utils/transformers and append it to the ordered transform() array in update-files.ts (position matters — e.g. transformCleanup runs last, transformNext is conditional).
- Install concerns plug in as updaters under utils/updaters invoked from addProjectComponents/addWorkspaceComponents in a fixed order (deps → tailwind config → env vars → fonts → files → css).
- New registry item types require extending registryItemTypeSchema/registryItemSchema (discriminated union on type) plus the FILE_TYPE_TO_CONFIG_KEY map that routes files to workspace targets, with parity on the site schema.
- Third-party registries: users add entries to components.json `registries` with {name}/{style}/${ENV} templated URLs (+ optional headers/params); `shadcn registry add` writes them and ensureRegistriesInConfig auto-discovers @namespaces from item addresses and the registries index.
- New CLI command: create a commander Command module under src/commands and addCommand it in index.ts; subcommand groups follow the commands/registry pattern (parent Command + .command children).
- New codemods: add a module under src/migrations and register it in commands/migrate.ts (existing: migrate-radix, migrate-icons, migrate-base-color, migrate-rtl, migrate-cn).
- New MCP tool: append to the tools[] literal in the ListToolsRequestSchema handler and add a case in handleCallTool (zod-parse args, return {content:[{type:'text',text}]}; RegistryError gets formatted with code/suggestion/context).
- Presets: DEFAULT_PRESETS in src/preset + encode/decode preset codes (isPresetCode/decodePreset) resolve to site /init URLs; the site's parseDesignSystemConfig + buildRegistryBase define the server half of the contract.
- Public registry directory: community registries are listed in apps/v4/registry/directory.json, validated by scripts/validate-registries.mts, and gated by the validate-registries workflow (reserved-namespace blocklist).
- Icon libraries are pluggable via src/icons (exported as shadcn/icons) + transform-icons + the iconLibrary config field; styles/themes/fonts/base-colors are data registries under apps/v4/registry consumed by both site and build.
- Agent-facing extension: skills/*/SKILL.md bundles (with rules/ and evals/) encode usage rules for AI tools — adding a skill is a docs-only change, separate from CLI code.
Contributor handoff
Most work happens in packages/shadcn (the CLI) and apps/v4 (site + registry authoring). Read the entrypoint, the two big command actions, the resolver/fetcher layer, and the updater pipeline before editing. Build the CLI before running e2e. Respect the invariants: CSS writes last, registry context cleared in finally, globalThis.fetch in proxy.ts, generated public/r is not hand-edited, and components.json is user-owned.
- Read-first files: packages/shadcn/src/index.ts (command table), commands/init.ts + commands/add.ts (the two complex actions), utils/add-components.ts (install pipeline), registry/resolver.ts + registry/fetcher.ts + registry/builder.ts (fetch/addressing), registry/schema.ts (all zod contracts), utils/updaters/update-files.ts (write+transform).
- Dev loop: `pnpm install`; build CLI `pnpm --filter=shadcn build` (tsup); run CLI against local site `pnpm shadcn:dev` (REGISTRY_URL=http://localhost:4000/r, SHADCN_TEMPLATE_DIR=../../templates) after `pnpm v4:dev`; unit tests `pnpm shadcn:test` (vitest run in packages/shadcn).
- E2e tests require the built dist and the v4 site: root `pnpm test` = `pnpm --filter=v4 registry:build` + start-server-and-test on http://localhost:4000 + `turbo run test`; packages/tests executes dist/index.js — editing src without rebuilding silently tests stale code.
- Conventions: zod schemas validate every external input (options schemas, registryItemSchema, rawConfigSchema); interactivity via `prompts`; output via logger/highlighter/spinner wrappers; errors are typed RegistryError subclasses with code/suggestion/context; deepmerge for config merges.
- Import alias: packages/shadcn source imports use '@/src/...' resolved by tsconfig paths + vite-tsconfig-paths in vitest — keep new imports on the alias, not relative chains.
- Trap: registry/proxy.ts must keep calling globalThis.fetch (not undici's) so msw interception works in tests; redirects are manual — don't replace with redirect:'follow' or private-registry headers will leak cross-origin.
- Trap: every command action must end with clearRegistryContext() in finally (headers/env live in AsyncLocalStorage); new commands that fetch registry items need the same discipline.
- Trap: write CSS last — updateCss is intentionally after updateFiles so file watchers rebuild once; preserve ordering when touching the updater pipeline. Also preserve the components.json FILE_BACKUP_SUFFIX backup/restore dance in init.
- Do not hand-edit generated artifacts: apps/v4/public/r/**, registry/__index__.tsx, registry/bases/__index__.tsx, styles/<style>/ui/** are build-registry.mts outputs; edit authored sources under registry/bases, registry/styles/*.css, examples/, and registry/new-york-v4 instead.
- Release flow: changesets (`pnpm release` = changeset version); publishing is `cd packages/shadcn && pnpm pub:beta|pub:rc|pub:next|pub:release`; release.yml auto-publishes prereleases for PRs labeled 'release: beta'/'release: rc'; commits use conventional-commit lint (.commitlintrc.json).
- Env vars that matter: REGISTRY_URL (registry base), SHADCN_TEMPLATE_DIR (template lookup), SHADCN_GITHUB_URL (template repo override), GH_TOKEN/GITHUB_TOKEN (github: item auth), HTTPS_PROXY/HTTP_PROXY/ALL_PROXY (fetch proxying), plus .env* files loaded per-command for ${ENV} expansion.
- Schema parity: registry/schema.ts is published as shadcn/schema and imported by apps/v4 (registryItemSchema in build-registry.mts and the /init route) — changing item shape requires updating both sides and the 871 committed public/r artifacts via a rebuild.
Languages
| Language | Files | Share of source |
|---|---|---|
| TypeScript | 3869 | 98% |
| JSON | 1009 | docs/config |
| Markdown | 378 | docs/config |
| JavaScript | 70 | 2% |
| CSS | 69 | docs/config |
| YAML | 52 | docs/config |
| HTML | 5 | docs/config |
| TOML | 3 | 0% |
Start reading here
Probable source entrypoint: packages/ — inferred from the directory tree and manifest, not the upstream docs.
Source roots
packages/ (704 files)
Repository structure
| Top-level path | Files |
|---|---|
apps/ | 2976 |
packages/ | 704 |
templates/ | 248 |
skills/ | 23 |
.github/ | 20 |
.claude/ | 2 |
.changeset/ | 2 |
scripts/ | 1 |
Docs found: CONTRIBUTING.mdLICENSE.mdREADME.md
Package
package.json — ui v0.0.1, 36 declared dependencies.
@babel/core@changesets/changelog-github@changesets/cli@commitlint/cli@commitlint/config-conventional@ianvs/prettier-plugin-sort-imports@manypkg/cli@types/hast@types/node@types/react@types/react-dom@typescript-eslint/eslint-plugin
CI & config entrypoints
README.md— Repository readme.github/workflows/browser-tests.yml— GitHub Actions workflow.github/workflows/code-check.yml— GitHub Actions workflow.github/workflows/issue-stale.yml— GitHub Actions workflow.github/workflows/monitor-registries.yml— GitHub Actions workflow.github/workflows/prerelease-comment.yml— GitHub Actions workflow.github/workflows/release.yml— GitHub Actions workflow.github/workflows/signed-commits-close.yml— GitHub Actions workflow.github/workflows/signed-commits.yml— GitHub Actions workflow.github/workflows/templates.yml— GitHub Actions workflow
Structural health signals
Average score 6.3/10 across 96 analyzed source files; 60 hotspots surfaced.
apps/v4/registry/bases/aria/examples/attachment-example.tsx— 1307 lines, score 5 (Large file, Deep nesting)apps/v4/registry/bases/base/examples/attachment-example.tsx— 1308 lines, score 5 (Large file, Deep nesting)apps/v4/registry/bases/radix/examples/attachment-example.tsx— 1294 lines, score 5 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
98a1fe67b4392026-09-21 — docs(registry): update the @7ovr description (#11972)1add195262d82026-09-21 — feat(registry): add ten community registries (#11976)cd80d215ef352026-09-21 — chore(ci): close PRs with unverified commits after 14 days (#11977)a87a63b2ca252026-09-17 — feat(registry): add nine community registries (#11925)ec6490f1740b2026-09-17 — feat(registry): add nine community registries (#11924)