Proper UI

components.json

components.json is an optional configuration file for the Proper UI CLI, used to customize import aliases and file paths for your project.

Overview

components.json tells npx @properui/cli@latest add two things: where your Tailwind config and CSS files live, and which import alias each kind of file should use when it's copied into your project. The CLI only ever reads this file — it never writes to it after init creates it.

Creating components.json

npx @properui/cli@latest init writes one for you, detected from your tsconfig.json and project structure. To create it by hand instead, add it at your project root:

// components.json
{
    "$schema": "https://properui.dev/schema.json",
    "style": "default",
    "tsx": true,
    "tailwind": {
        "css": "app/globals.css",
        "theme": "styles/theme.css",
        "prefix": ""
    },
    "aliases": {
        "components": "@/components",
        "utils": "@/utils",
        "ui": "@/components/base",
        "hooks": "@/hooks"
    },
    "registry": "https://properui.dev/r"
}

Configuration options

FieldTypeDescription
$schemastringJSON Schema URL, used for editor autocomplete and validation.
stylestringReserved for future visual style presets. Currently always "default".
tsxbooleanWhether the CLI writes .tsx/.ts files (true) or plain .jsx/.js (false).
tailwind.cssstringPath to the CSS file that imports Tailwind — where init appends the @source line.
tailwind.themestringPath to the file that holds your @theme token block.
tailwind.prefixstringOptional Tailwind class prefix, if your project namespaces its utility classes.
aliasesobjectImport alias per file category — see below.
registrystringBase URL or local directory the CLI fetches component and example JSON from.

registry defaults to https://properui.dev/r, which goes live once the docs site deploys. Until then, point it at a locally built registry — see the CLI's registry section.

aliases

Every file the CLI copies belongs to one of four categories, each with its own alias:

AliasUsed forDefault
componentsApplication, marketing, and example components@/components
uiBase components (buttons, inputs, badges, …)@/components/base
utilsShared helpers like cx and demo-assets@/utils
hooksHooks like use-breakpoint and use-clipboard@/hooks

examples

A project that keeps base components under src/ui instead of src/components/base only needs to override that one alias:

{
    "aliases": {
        "components": "@/components",
        "utils": "@/utils",
        "ui": "@/ui",
        "hooks": "@/hooks"
    }
}

How aliases work

1. Import statement transformation

Every file in the registry imports its neighbors using the package's own @/ alias — import { cx } from "@/utils/cx". When the CLI copies a file into your project, it rewrites each of those imports to the alias configured in components.json, so @/utils/cx becomes @workspace/utils/cx if that's what you've configured, with no manual find-and-replace required.

2. File path resolution

Separately, the CLI needs to know where on disk each alias actually points, so it can write the file to the right directory. It reads that mapping from your tsconfig.json (or jsconfig.json) compilerOptions.paths — the same paths your editor and bundler already use to resolve the alias at compile time.

Alias requirements

Why relative paths don't work

An alias like ../../../components/ui might resolve correctly from one file, but the CLI writes the same import into many files at different depths — a relative path that's correct in one place breaks in another. Aliases in components.json must be tsconfig path aliases, which resolve the same way regardless of which file imports them.

Correct setup

Match your components.json aliases to real entries in tsconfig.json:

// tsconfig.json
{
    "compilerOptions": {
        "paths": {
            "@/*": ["./src/*"]
        }
    }
}
// components.json
{
    "aliases": {
        "components": "@/components",
        "utils": "@/utils",
        "ui": "@/components/base",
        "hooks": "@/hooks"
    }
}

If the CLI can't resolve an alias against your tsconfig.json, it stops before writing anything and tells you which alias is missing.

Example configurations

Standard Next.js project

{
    "$schema": "https://properui.dev/schema.json",
    "style": "default",
    "tsx": true,
    "tailwind": {
        "css": "app/globals.css",
        "theme": "styles/theme.css",
        "prefix": ""
    },
    "aliases": {
        "components": "@/components",
        "utils": "@/utils",
        "ui": "@/components/base",
        "hooks": "@/hooks"
    },
    "registry": "https://properui.dev/r"
}

Monorepo with shared UI package

{
    "$schema": "https://properui.dev/schema.json",
    "style": "default",
    "tsx": true,
    "tailwind": {
        "css": "../../apps/web/app/globals.css",
        "theme": "styles/theme.css",
        "prefix": ""
    },
    "aliases": {
        "components": "@workspace/ui/components",
        "utils": "@workspace/ui/utils",
        "ui": "@workspace/ui/components/base",
        "hooks": "@workspace/ui/hooks"
    },
    "registry": "https://properui.dev/r"
}

Run this from packages/ui, so components land in the shared package rather than in the app that happens to run the command. See the monorepo guide for the matching tsconfig.json paths.

Vite project with custom alias

{
    "$schema": "https://properui.dev/schema.json",
    "style": "default",
    "tsx": true,
    "tailwind": {
        "css": "src/index.css",
        "theme": "src/styles/theme.css",
        "prefix": ""
    },
    "aliases": {
        "components": "@/components",
        "utils": "@/lib/utils",
        "ui": "@/components/ui",
        "hooks": "@/lib/hooks"
    },
    "registry": "https://properui.dev/r"
}

FAQs