Copy components with the CLI
Copy a component's source into your project with npx hone-ui add, and compare it with upstream.
The CLI copies components into your project so you can edit them. The npm package and the CLI come from the same source files, so a copied component matches the released one.
npx hone-ui init
npx hone-ui list
npx hone-ui add button
npx hone-ui diff buttonEvery hint the CLI prints names your package manager. It reads the lockfile in the project folder
or a parent folder, so a pnpm project sees pnpm add @hone-ui/react react-aria-components and
pnpm dlx hone-ui add button. Yarn 2 and newer get yarn dlx, Yarn 1 gets npx, Bun gets bunx,
and a project without a lockfile gets npm and npx. This page writes npx throughout.
Run hone-ui <command> --help for the usage, the flags and an example of one command.
Find a component with list
list prints the name of every component you can add, in columns. The shared internal-* files
stay out, because they come with the components that use them. add and diff suggest up to
three close names for a typo and point at list:
npx hone-ui add buton
# Unknown component: buton.
# Did you mean button?Set up with init
Run init once, from the folder with your package.json. It writes two files and copies no styles.
hone-ui.jsonrecords the folder for copied files and the look.addanddiffread it, so you never repeat--dir.src/components/ui/hone-ui/hone-ui.cssimports@hone-ui/react/styles/material.core.cssfrom the package. The core holds the theme variables, the page baseline and shared helpers but no component styles, so no copied component's CSS ships twice. Importhone-ui.cssonce at the root of your app, in place of@hone-ui/react/styles/material.css.
{
"dir": "src/components/ui/hone-ui",
"look": "material",
"css": "src/components/ui/hone-ui/hone-ui.css"
}Pass --dir to pick another folder and --look minimal for the Minimal look. Without --look,
init uses the look your imports of @hone-ui/react/styles already name, and material when
they name none. init also
imports the styles of components you copied before it ran, and names any of @hone-ui/react and
react-aria-components that your package.json lacks. It stops if hone-ui.json exists, and it
keeps a hone-ui.css that exists. Earlier versions copied the core into a look folder. init leaves such a file alone and tells you it can go.
What add copies
Each command copies the component folder and everything it imports.
- The component's
.tsxfile, itsindex.ts, its base style file (.base.css) plus the style file of the look inhone-ui.json(.material.cssor.minimal.css, never both), and its AI guide. The style files are plain CSS, compiled from the library's Tailwind source, so your app needs no Tailwind. - Parts that belong with it.
cardbrings CardContent, CardActions, CardHeader and CardMedia.dialogbrings Backdrop and the four dialog parts.selectbrings MenuItem, andradiobrings RadioGroup and FormControlLabel. - Shared files from
internal,systemandstylesthat only copied code uses, kept in the same folder layout, so every relative import still resolves.
The theme and the sx resolver are the exception. The copied files never include the theme
context, its hooks or resolveSx. They import useTheme from @hone-ui/react/styles and resolveSx
from @hone-ui/react/system, so a ThemeProvider from the package reaches every copied
component, including a function sx. That also keeps the theme code out of your copy. The FormControl context stays a
copy, because only copied components read it. A custom input that calls useFormControl from
@hone-ui/react does not see a copied FormControl, so import useFormControl from the copy.
Files land in src/components/ui/hone-ui unless hone-ui.json or --dir names another folder. Names are not case sensitive,
so IconButton and icon-button both work.
Options
| Option | Command | Description |
|---|---|---|
-d, --dir <path> | init, add, diff | The folder for copied files, relative to the folder that holds hone-ui.json. Wins over hone-ui.json. |
-l, --look <name> | init | material or minimal. Defaults to the look your imports of @hone-ui/react/styles use, and to material when they name none. |
-o, --overwrite | add | Replace files that you changed. Lists each file it replaced. |
Each command accepts only its own options. Any other flag, such as add --look minimal or
init --overwrite, stops with an error that names the flag and the command. A flag that no
command knows stops with Unknown option --bogus.
Without --overwrite, add leaves a file alone when its content differs from the registry and
lists it at the end. Files that already match are not touched.
Switch looks
Change look in hone-ui.json, then run add with no names:
npx hone-ui addWith no names, add re-syncs every component you already copied. For each one it copies the new
look's style file, deletes the old look's style file when it still matches the registry, and
updates the imports in hone-ui.css, including the core import.
add button after a switch touches only button and the files it imports. It lists the other
components that still have the old look's styles, so you know to run add with no names.
A style file of the old look that you edited stays on disk, and its import stays in
hone-ui.css. The component still gets the new look's file, so both looks apply to it until you
delete the old file and its import. add names each file it kept and says why.
After copying
add and diff need a hone-ui.json. They look for it in the current folder, then in each
parent folder up to the repository root, and stop with an error that tells you to run init
when they find none. Paths in the config, and a relative --dir, start at the folder that holds
hone-ui.json. For init, that is the current folder.
add appends an @import for each copied style file to hone-ui.css, after the package's core
styles, so there is nothing to import by hand. It prints an install command for the packages that
your package.json does not list yet, and nothing when it lists them all. If a
write fails, add restores the files it had changed, so a failed run leaves no partial copy.
The copied components still need @hone-ui/react for the theme code that must stay shared, such
as ThemeProvider and useTheme, and they import their types from it. The core style sheet
replaces @hone-ui/react/styles/material.css for copied components only. Components you use
from the npm package need that package's look import as well.
Recreate a deleted hone-ui.css
If hone-ui.css is gone, run add. It writes the file again with the look's core import and an
@import for the style files of every component you copied, and says that it did. add with no
names does the same and re-syncs your components, and it works before you copied any component.
init still stops when hone-ui.json exists, and its error points at add when only the style
sheet is missing.
Copy one component into an existing app
An app that already uses @hone-ui/react can copy a single component and keep the rest from the
package. Import the package look first and hone-ui.css after it, so your copy wins:
@import '@hone-ui/react/styles/minimal.css';
@import './components/ui/hone-ui/hone-ui.css';hone-ui.css loads the look's core a second time. The core's base rules, such as the
ButtonBase reset, sit in the hone-ui.base sub-layer of hone-ui. Rules written directly in
hone-ui always beat a sub-layer, so the reset cannot wipe the background of a package
Button, whichever of the two files loads last.
That protection covers the resets only. Your edited copy beats the package's rules when
hone-ui.css loads last. If hone-ui.css loads first, the package look comes later and its rules
win over your copy, so keep the order shown above.
Use the package look import whenever the app renders components from @hone-ui/react. The core
that hone-ui.css imports has no component styles.
See what changed upstream
Copied files are yours, so nothing updates them. diff compares your copy with the registry
inside the CLI version you run, so use npx hone-ui@latest diff to compare with the newest
release.
npx hone-ui diff # lists the copied components that differ
npx hone-ui diff button # prints the changes to Button and the shared files it importsThe output is a unified diff. Lines starting with - are in your copy and lines starting with
+ are upstream. Your own edits show up too, because the CLI keeps no record of the version you
copied. diff also lists files that upstream has and your copy lacks. To take upstream, run
add with --overwrite, which replaces your edits in those files.
diff exits with code 1 when any copy differs, like git diff --exit-code, so a CI step can
fail on drift. In a project with no copied components it prints "Nothing copied yet" and exits
with code 0, because there is nothing to drift.
The registry
The registry follows the shadcn registry format. The build writes a registry.json index and one
JSON file per item into the CLI's dist/registry folder. Components are registry:ui items.
Each shared source file is its own registry:lib item. The looks have no items, because the
package serves their core style sheets. Every file carries a target under ~/components/ui/hone-ui, and
registryDependencies hold full URLs, because bare names would resolve against shadcn's own
registry and collide with its button and card.
Set HONE_REGISTRY_URL when you build to name the folder that serves the JSON, for example
HONE_REGISTRY_URL=https://example.com/r pnpm --filter hone-ui build. Until the registry has a
host, a placeholder address stands in, so npx shadcn add <url> does not work yet. The CLI
itself reads the bundled registry and needs no host.