Install
One command installs the complete set and its skin as source you own. The CSS entry is the only wiring you write.
Your agent can run all four steps from one prompt.
Install the set and its skin
One command copies every component, block, and primitive into your repository as source you own, together with the Refined skin that owns every token value they paint with.
npx shadcn@latest add https://control-ui.dev/r/all-refined.jsonA components/control-ui/ directory holding the source, styles/skin-theme.css and styles/skin.css holding the
pack, and a components.json the registry keeps writing to.
Every skin page exposes the same all-<skin> manifest, so the pack you start on is the one you name here.
Install one item at a time
A lean install picks the items instead. The skin stays required either way: it owns every token value the components paint with, and one pack always installs first.
npx shadcn@latest add https://control-ui.dev/r/skin-refined.json --overwritenpx shadcn@latest add https://control-ui.dev/r/chat-message.jsonBlocks install the same way and land as a complete composition — the chat block brings messages, input, attachments, markdown, composer controls, and actions.
npx shadcn@latest add https://control-ui.dev/r/chat-block.jsonNaming items instead of the set means the update and doctor scripts have to name them too.
Wire the CSS
Each item appends its own imports to the CSS entry named in components.json. Core plus a skin leaves you with:
/* app/globals.css */@import "tailwindcss";@import "../components/control-ui/styles/theme.css";@import "../components/control-ui/styles/skin-theme.css";@import "../components/control-ui/styles/skin.css";Then stamp the pack id on the root element:
<html data-skin="refined">…</html>A control paints with the skin's own radius and color, and one portalled surface — a popover, dialog, menu, or tooltip — opens carrying the same tokens. Both, or the wiring is not done.
Nothing paints? The registry always writes ../components/…, which resolves only when the entry sits one directory
above the components alias. Run node <install dir>/scripts/fix-css-imports.mjs to fold every appended line onto
this app's real paths, then verify with the doctor.
The contract
Exactly one pack owns skin.config.tsx, skin-theme.css, and skin.css. --overwrite claims all three — without
it the CLI keeps the previous pack's files in place.
The current skin contract is version 8: registered component CSS knobs carry every visual decision, sizing included, published one
slice per paint family under /r/contract/, indexed by /r/contract/index.json.
There is no token fallback when data-skin is missing or misspelled. It is the scope every token is declared under,
not a multi-skin switch, so one skin still needs it: without it every portalled surface renders with no tokens at all.
Compiled styles assume Safari 16.4+, Chrome 119+, Firefox 128+; the architecture guide's cascade section derives the floor.
Or install the package
The same set ships as @ctrl-ui/react with the Refined skin baked in, for apps that would rather pin a version
than own the source. Import paths mirror the registry tree: replace @/components/control-ui/ with
@ctrl-ui/react/ in any example on this site.
bun add @ctrl-ui/react/* app/globals.css */@import "tailwindcss";@import "@ctrl-ui/react/styles/index.css";@source "../node_modules/@ctrl-ui/react/dist";Stamp data-skin="refined" on the root element as above. Other skins, the update script, and the doctor stay
registry-only.