Before you start#
- Nothing is replaced on install. shadcn/ui lives in
components/ui/, PixilUI incomponents/pixilui/, and neither overwrites the other. - PixilUI reads its own tokens from
foundation.css, not your shadcn/ui theme variables. A PixilUI button next to a shadcn/ui card will use the PixilUI colours, radii and shadows. The theming page says how to point the PixilUI roles at your brand. - PixilUI overlays use Base UI, not Radix. They do not share context with Radix components, so move a dialog and the buttons inside it together.
- Migrate a whole screen at a time. Mixing the two sets of tokens on one surface is the one thing that looks wrong.
What maps to what#
| shadcn/ui | PixilUI | What changes |
|---|---|---|
Button |
button |
variant: default is primary, destructive is danger, secondary and ghost keep their names. outline and link have no match: keep shadcn/ui for those. size: default is md. loading shows a spinner in place of the icon and keeps focus; icons go in icon and iconEnd, not in the children. |
Dialog |
dialog |
The trigger is a prop (trigger={<Button>…</Button>}), the content is the children. DialogTitle and DialogDescription keep their names. DialogFooter with two buttons becomes DialogActions confirm="Delete agent" onConfirm={…}, which shows pending and error itself when onConfirm returns a promise. |
AlertDialog |
dialog with variant="danger" |
One component for both. If the thing being confirmed is an action an agent wants to take, use approval-card instead. |
Sheet, Drawer |
drawer |
side is right, left or bottom (no top). DrawerTitle, DrawerDescription, DrawerActions and DrawerClose match the dialog parts. |
Popover |
popover |
The trigger is a prop; side, align and offset work as in shadcn/ui. PopoverTitle, PopoverDescription and PopoverClose are optional. |
Tooltip |
tooltip |
The text is label, the control is the child, and shortcut shows the key. TooltipProvider is optional: a lone tooltip groups itself. |
Input, Label |
input |
Replaces the input, its label and the help line: label, description and error are props and are wired to the field for screen readers. prefix, suffix and clearable replace hand-built adornments. |
The AI parts of a screen usually have no shadcn/ui equivalent: an approval step before the agent acts, the steps of a run, tool calls, a thinking line, a streaming answer. See the AI Elements guide if you built those with AI Elements, or start from components.
Steps#
1. Add the registry and the foundation
npx pixilui@latest initThis adds @pixilui to registries in components.json next to your other entries, installs the foundation and writes the agent rules. Your components/ui/ folder is not touched.
2. Add the items the screen needs
npx pixilui@latest add button dialog input3. Swap the imports on one screen
// Before
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
// After
import { Button } from "@/components/pixilui/button";
import { Dialog, DialogActions, DialogDescription, DialogTitle } from "@/components/pixilui/dialog";A search for @/components/ui/button in that screen's folder finds every place to change.
4. Change the props
// Before
<Dialog>
<DialogTrigger asChild>
<Button variant="destructive">Delete agent</Button>
</DialogTrigger>
<DialogContent>
<DialogTitle>Delete the Billing agent?</DialogTitle>
<DialogDescription>Its 214 runs and 3 schedules go with it.</DialogDescription>
<DialogFooter>
<Button variant="outline">Cancel</Button>
<Button variant="destructive" disabled={pending} onClick={remove}>Delete agent</Button>
</DialogFooter>
</DialogContent>
</Dialog>
// After
<Dialog variant="danger" trigger={<Button variant="secondary">Delete agent</Button>}>
<DialogTitle>Delete the Billing agent?</DialogTitle>
<DialogDescription>Its 214 runs and 3 schedules go with it.</DialogDescription>
<DialogActions confirm="Delete agent" onConfirm={remove} />
</Dialog>remove returns a promise; the dialog shows the pending state, closes on success and shows the error on failure. Drop the pending state and the disabled flag you kept for it.
5. Check the tokens around it
Classes you wrote around the components keep working, but replace shadcn/ui theme classes on the same surface with the PixilUI roles: bg-card becomes bg-surface, text-muted-foreground becomes text-secondary, border stays border-border. In a shadcn/ui app, bg-accent is the grey hover of menus; the PixilUI brand fill is bg-accent-solid.
6. Remove what nothing imports
When no file imports components/ui/dialog any more, delete it. Keep the shadcn/ui components you still use, such as tables, forms and menus; they are fine next to PixilUI.
With an agent#
Paste this into Claude Code, Cursor or Codex:
Read https://pixilui.com/docs/migrate-from-shadcn-ui/markdown. Move the settings screen from shadcn/ui Button, Dialog and Input to PixilUI using that mapping, one file at a time, and list what you changed.