# How shadcn Resolves Component Dependencies and Builds Installation Trees

> Discover how shadcn resolves component dependencies and builds installation trees. Learn about recursive fetching, topological sorting, and dependency guarantees.

- Repository: [shadcn-ui/ui](https://github.com/shadcn-ui/ui)
- Tags: internals
- Published: 2026-02-26

---

**shadcn‑ui's CLI resolves component dependencies by recursively fetching registry definitions, topologically sorting the results using Kahn's algorithm, and merging them into a single installation tree that guarantees dependencies are installed before their dependents.**

The `shadcn` CLI automates component installation by analyzing dependency graphs across the registry. When you run `npx shadcn add button`, the tool performs complex dependency tree resolution to ensure that underlying primitives, hooks, and utility files are installed automatically. Understanding how shadcn resolves component dependencies reveals the deterministic pipeline that powers the `add` command.

## The Dependency Resolution Pipeline

The core resolution logic lives in [`packages/shadcn/src/registry/resolver.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/registry/resolver.ts). The process begins with `resolveRegistryTree`, which orchestrates three distinct phases: fetching initial items, recursively resolving nested dependencies, and sorting the final payload.

### Fetching Initial Registry Items

The entry point `resolveRegistryTree` receives an array of component names and the project configuration. It deduplicates the input and fetches the corresponding JSON definitions from the registry.

```typescript
export async function resolveRegistryTree(
  names: RegistryItem["name"][],
  config: Config,
  options: { useCache?: boolean } = {}
) {
  const uniqueNames = Array.from(new Set(names))
  const results = await fetchRegistryItems(uniqueNames, config, options)
  // results → parsed RegistryItem objects
}

```

*Source*: [`packages/shadcn/src/registry/resolver.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/registry/resolver.ts) (lines 24‑34).

The helper `fetchRegistryItems` routes each identifier through different resolution strategies:

- **Local files**: Resolved via `fetchRegistryLocal`
- **Direct URLs**: Fetched with a single HTTP request
- **Namespaced items** (e.g., `@custom/ui/card`): Converted to full URLs using `resolveRegistryItemsFromRegistries` with custom headers from config
- **Plain component names**: Treated as style-scoped items (`styles/${style}/${name}.json`)

*Source*: [`packages/shadcn/src/registry/resolver.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/registry/resolver.ts) (lines 68‑100).

### Recursively Resolving Dependencies

Once an item is fetched, `resolveDependenciesRecursively` inspects its `registryDependencies` array. This function handles three identifier types and builds two collections: resolved registry items and pending registry names that require URL resolution.

```typescript
async function resolveDependenciesRecursively(
  dependencies: string[],
  config: Config,
  options: { useCache?: boolean } = {},
  visited: Set<string> = new Set()
) {
  for (const dep of dependencies) {
    if (dep.startsWith("@") && config?.registries) {
      // Namespaced registry item → fetch with config headers
      const [item] = await fetchRegistryItems([dep], config, options)
      // Recurse into its own registryDependencies
    } else if (isUrl(dep) || isLocalFile(dep)) {
      // Direct URL or local JSON → fetch and recurse
    } else {
      // Plain component name → queue for style-based resolution
    }
  }
}

```

*Source*: [`packages/shadcn/src/registry/resolver.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/registry/resolver.ts) (lines 66‑124).

A **visited Set** prevents infinite loops when circular dependencies exist. Namespaced items trigger a header-aware fetch, throwing `RegistryNotConfiguredError` if the registry is undefined in the config. Plain component names are collected in a `registryNames` array for batch processing.

### Resolving Plain Names to URLs

After the recursive walk collects all plain component names, `resolveRegistryDependencies` converts them to final URLs. It determines the target style from the configuration and maps each name to a registry endpoint.

```typescript
async function resolveRegistryDependencies(url: string, config: Config) {
  const { registryNames } = await resolveDependenciesRecursively(
    [url], config, {}, new Set()
  )
  const style = await getTargetStyleFromConfig(config)
  const urls = registryNames.map(name => 
    resolveRegistryUrl(`styles/${style}/${name}.json`)
  )
  return Array.from(new Set(urls))
}

```

*Source*: [`packages/shadcn/src/registry/resolver.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/registry/resolver.ts) (lines 75‑99).

## Building the Installation Tree

After gathering all items, the resolver constructs a deterministic installation tree through hashing, topological sorting, and deep merging.

### Topological Sorting with Kahn's Algorithm

Each item receives a deterministic hash via `computeItemHash` that incorporates its name and source URL. This ensures that the same component from different registries is treated as distinct. The resolver then builds a dependency graph and applies `topologicalSortRegistryItems`, which implements **Kahn's algorithm** to produce a linear order where dependencies always precede dependents.

```typescript
payload = topologicalSortRegistryItems(payload, sourceMap)

```

*Source*: [`packages/shadcn/src/registry/resolver.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/registry/resolver.ts) (lines 22‑24, 86‑142).

If a circular dependency is detected, the algorithm emits a warning and appends the remaining items in arbitrary order (lines 22‑41).

### Merging and Deduplicating Resources

The sorted payload is merged into a single `registryResolvedItemsTree` structure. The resolver aggregates:

- `dependencies` and `devDependencies` (deep‑merged)
- `files` (deduplicated by target path via `deduplicateFilesByTarget`)
- Tailwind configuration, CSS variables, CSS, documentation, and fonts

The final structure conforms to `registryResolvedItemsTreeSchema`:

```typescript
{
  dependencies: { /* package names and versions */ },
  devDependencies: { /* package names and versions */ },
  files: [{ path, type, target }, …],
  tailwind: { /* config extensions */ },
  cssVars: { /* variable definitions */ },
  css: { /* custom styles */ },
  docs: "markdown content",
  fonts: [{ name, type: "registry:font", font: … }]
}

```

*Source*: [`packages/shadcn/src/registry/resolver.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/registry/resolver.ts) (lines 46‑58, 148‑158).

## CLI Integration and Execution

The `addComponents` function in [`packages/shadcn/src/utils/add-components.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/utils/add-components.ts) serves as the CLI entry point. It invokes `resolveRegistryTree`, validates the generated files, and executes a series of updaters on the merged tree.

```typescript
let tree = await resolveRegistryTree(components, configWithDefaults(config))
await updateFiles(tree.files, config, { overwrite, silent })
await updateDependencies(tree.dependencies, tree.devDependencies, config, { silent })

```

*Source*: [`packages/shadcn/src/utils/add-components.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/utils/add-components.ts) (lines 84‑102, 124‑132).

This ensures that when you install a component, all Tailwind configs, CSS variables, and file dependencies are applied in the correct order.

## Practical Code Examples

### Resolving a Component from the CLI

When you execute:

```bash
npx shadcn add button

```

The CLI internally calls:

```typescript
const tree = await resolveRegistryTree(
  ["button"],
  configWithDefaults(config)
)
// tree contains button + all registryDependencies

```

*Source*: [`packages/shadcn/src/utils/add-components.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/utils/add-components.ts) (line 84) and [`packages/shadcn/src/registry/resolver.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/registry/resolver.ts) (line 24).

### Using the Resolver Programmatically

You can invoke the resolver directly in Node.js to inspect dependencies before installation:

```typescript
import { resolveRegistryTree } from "@/src/registry/resolver"
import { configWithDefaults } from "@/src/registry/config"

async function getTree() {
  const cfg = await configWithDefaults(/* your config */)
  const tree = await resolveRegistryTree(
    ["alert-dialog", "@custom/ui/card"],
    cfg,
    { useCache: false }
  )
  console.log(tree.files) // Files to be written
  console.log(tree.dependencies) // NPM packages required
}

```

*Source*: [`packages/shadcn/src/registry/resolver.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/registry/resolver.ts) (lines 24‑34, 88‑140).

### Inspecting Dependency Order

To verify the exact installation sequence:

```typescript
import { topologicalSortRegistryItems } from "@/src/registry/resolver"

const sorted = topologicalSortRegistryItems(payload, sourceMap)
sorted.forEach(item => console.log(item.name))

```

The output lists items in the precise order the CLI applies updates, ensuring that every dependency is installed before the component that requires it.

*Source*: [`packages/shadcn/src/registry/resolver.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/registry/resolver.ts) (lines 86‑142).

## Summary

- **Dependency discovery** begins with `resolveRegistryTree` in [`packages/shadcn/src/registry/resolver.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/registry/resolver.ts), which fetches component definitions and their metadata.
- **Recursive resolution** via `resolveDependenciesRecursively` handles namespaced items (`@registry/name`), direct URLs, local files, and plain component names while preventing circular dependencies using a visited Set.
- **Topological sorting** applies Kahn's algorithm to ensure dependencies always appear before dependents in the installation queue.
- **Tree merging** aggregates all configured resources—files, NPM dependencies, Tailwind config, and CSS variables—into a single `registryResolvedItemsTree`.
- **CLI execution** in `addComponents` validates and applies the resolved tree to the project filesystem.

## Frequently Asked Questions

### How does shadcn handle circular dependencies?

The resolver passes a `visited` Set through `resolveDependenciesRecursively` to track already-processed items. If a circular reference is detected during topological sorting, Kahn's algorithm emits a warning and appends the remaining items in arbitrary order rather than failing.

### What is the difference between namespaced and plain component names?

Namespaced items follow the `@registry/name` pattern and require a matching registry configuration in [`components.json`](https://github.com/shadcn-ui/ui/blob/main/components.json), allowing custom headers and endpoints. Plain component names are resolved against the default shadcn registry using the project's configured style (e.g., [`styles/new-york/button.json`](https://github.com/shadcn-ui/ui/blob/main/styles/new-york/button.json)).

### How does the CLI ensure dependencies are installed before dependents?

After fetching all registry items, the resolver assigns each a unique hash and builds a dependency graph. It then runs `topologicalSortRegistryItems`, which uses Kahn's algorithm to produce a linear ordering where every component appears after its `registryDependencies` entries.

### Can I resolve dependencies without installing them?

Yes. Import `resolveRegistryTree` from [`packages/shadcn/src/registry/resolver.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/registry/resolver.ts) and call it with your configuration. This returns the full `registryResolvedItemsTree` containing all files, dependencies, and configuration updates without modifying your project, allowing you to preview the installation impact.