Installation

philcn is not a package you install. The command copies source files into your project, where they become yours.

What you need

  • A React project — React 19 or later.
  • Tailwind CSS v4. The components are written in its classes.
  • TypeScript, if you want the types. The files are .tsx.

Set the project up

Once per project:

npx philcn@latest init

It writes three things:

components.jsonWhere your components live and which aliases they use. The same file shadcn reads, so a project already set up for shadcn keeps working.
philcn.cssThe theme: colours, radii and animations, for light and dark.
lib/utils.tsHolds cn, which every component and every pasted block uses. A project that already has this file keeps its own.

The stylesheet

Import it once, wherever your application brings in its CSS. It opens with two lines, and the second one matters:

@import "tailwindcss";
@import "philcn/source.css";

The second line tells Tailwind to look inside the installed package. Some of the shared behaviour carries Tailwind classes, and Tailwind does not scan node_modules on its own — without that line, menus and dialogs arrive unstyled. init writes it for you.

Add components

npx philcn@latest add button dialog

Each one lands in your own source tree, with its imports rewritten to your aliases. From then on the file is yours: change it, and nothing will overwrite it.

To see everything on offer, ask for the list:

npx philcn@latest list

Packages

philcn has two dependencies, both optional, and the command tells you when a component needs one:

@floating-ui/react-domWorks out where a floating panel goes and what it does when the window edge is in the way. Needed by anything that floats: Popover, Tooltip, Select, the menus.
react-hook-formThe machinery of a form: values, touched fields, validation. Needed by Form alone.

Neither draws anything. A project using nothing that floats and no form installs neither.

Coming from shadcn

The API is the same on purpose: same component names, same props. A block copied from shadcn that imports from @/components/ui/… — which is most of them — needs no change at all, once philcn add has written those files. A block copied out of their registry carries a registry path, and that is the one line to change:

import { Button } from "@/registry/new-york/ui/button"
// becomes
import { Button } from "@/components/ui/button"

Both spellings of the render-as-something-else prop are accepted: asChild, from blocks written against Radix, and render, from the current shadcn documentation. Neither vintage of a block has to be rewritten.

Carrying every line

By default the shared behaviour behind a component — focus traps, portals, keyboard navigation — is imported from the philcn package rather than copied, the same way a shadcn component imports Radix. If a project has to hold every line it runs, ask for the copies too:

npx philcn@latest add dialog --standalone

Nothing is then imported from philcn at runtime.