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 initIt writes three things:
| components.json | Where your components live and which aliases they use. The same file shadcn reads, so a project already set up for shadcn keeps working. |
| philcn.css | The theme: colours, radii and animations, for light and dark. |
| lib/utils.ts | Holds 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 dialogEach 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 listPackages
philcn has two dependencies, both optional, and the command tells you when a component needs one:
| @floating-ui/react-dom | Works 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-form | The 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 --standaloneNothing is then imported from philcn at runtime.