Migrating a custom project
Your app has its own components, its own CSS or another UI kit. Here is how to move it onto Koala one surface at a time, by hand or with a coding agent, without changing what it does.
With a coding agent#
The Koala skill carries this page, so an agent can run the whole migration. Install the skill in your project, then start a new session.
npx koalaui-cli@latest skillThen ask for the audit first:
Migrate this project to Koala UI. Follow references/migrate.md in the koala-ui skill.
Start with the audit and write the plan to koala-migration.md. Change nothing until I approve it.The agent stops at the plan so you can review it, then works through it and ticks off each surface. The plan lives in your repo, so a later session picks up where the last one stopped. Cursor, Codex and other agents need one line pointing at the skill, see AI and agents.
Before you start#
| You need | If you do not have it |
|---|---|
| React 19 | Upgrade React first, as its own change. |
| Tailwind CSS v4 | Upgrade from v3 first. On a project without Tailwind, add it, then read Keeping both working below. |
The @/* path alias | Add it to tsconfig.json, and to vite.config.ts in Vite. init checks it. |
| Next.js or Vite | init detects both. Anywhere else it prints the steps to wire by hand. |
| A clean branch | Commit or stash first, so each step of the migration is one reviewable diff. |
Step 1: Audit#
Read the project before touching it, and record what you find:
- The stack. Framework, React and Tailwind versions, the styling method (CSS Modules, Sass, styled-components, Emotion, a kit such as MUI, Chakra, Ant or Bootstrap), and the checks the project has: tests, type checking, lint, build.
- The surfaces. Every reusable UI component, or every kit component in use, with how many files import it. Usage count sets the order.
- The structure. The kind of product, each screen’s regions and their order, how navigation works, what scrolls, what happens on small screens, and whether it runs on touch.
- The brand. The primary color, neutrals, radius, fonts and any dark mode, read from the existing tokens or the most repeated values.
- What is yours. Domain components that mean something only in this product, such as a shipment timeline or a seat map. Koala does not replace them.
Step 2: Plan#
Write koala-migration.md at the project root and get it approved before any code changes. Each surface gets its Koala counterpart from the maps below, its tier from npx koalaui-cli@latest list, and a place in the order. Anything with no counterpart stays and moves to tokens only.
# Koala migration
Stack: Next.js 16, React 19, Tailwind 3, CSS Modules
Blockers: Tailwind 3 to 4
## Brand
Primary #0f766e, radius 12px, Inter
## Structure
Point of sale on a touch screen: density compact, controls size lg
Regions: top nav, open tickets, product grid, ticket panel
Decision: keep (approved by the owner)
Shell: LayoutTopbar + Navbar (Pro, ask), three LayoutPane columns
## Surfaces, in order
- [ ] Button (41 uses): src/components/Btn.tsx > Button
- [ ] Text field (18 uses): src/components/Input.tsx > Input, Field
- [ ] Modal (6 uses): src/components/Modal.tsx > Dialog
- [ ] Data grid (3 uses): react-table > Data Table (Pro, ask)
## Stays as it is
src/components/CashCount.tsx: domain component, tokens onlyMark every Pro item and let the project’s owner choose: buy a license, compose the free components, or keep what exists. Never rebuild a Pro component from its docs or preview.
The plan also records one decision the owner has to make about the structure:
| Decision | What it means | When |
|---|---|---|
| Keep | Same screens, regions and order. Only the pieces that draw them change. | The default, and the right call for tools people use all day. |
| Reorganize | Listed changes only, each with its reason and its own commit. | When the owner asks, or approves a specific problem you found. |
Step 3: Set up the foundation#
init adds koala.css, lib/utils.ts, lib/tv.ts, the theme provider and koala.json, and leaves your components alone. Follow the steps it prints for the fonts and the provider, and resolve every warning it gives.
# Tokens, the lib helpers and the theme provider
npx koalaui-cli@latest init
# Only if the plan uses Pro components
npx koalaui-cli@latest login <your-key>
# The free components the plan needs
npx koalaui-cli@latest add button input field dialogIf you already have a lib/utils.ts, init keeps it. Merge Koala’s version in with npx koalaui-cli@latest diff utils: lib/tv.ts imports twMergeConfig from it. If you have a components/ui/button.tsx of your own, add refuses to run until it is moved. Move those files to components/legacy/, update their imports, and commit that on its own.
Step 4: Bring your brand#
Set your brand once, on the tokens, before swapping any component. After that every Koala component arrives in your colors. Put this after the import of koala.css:
:root {
--brand: #0f766e;
--radius: 0.75rem;
}A light brand color also needs a dark --brand-foreground. Load your fonts as --font-sans and --font-heading. An app with one look pins it with <ThemeProvider forcedTheme="light">. For a palette of your own, see Theming.
Step 5: Swap one surface at a time#
- Start at the bottom. Buttons and text fields first, then overlays, then the pieces inside navigation. Each layer leans on the one before it, and the shell comes last.
- One surface, one commit. Add the component, read its reference, replace every use, and remove the old one. Run the project’s checks before moving on.
- Translate props, keep behavior. Handlers, validation, routes, disabled conditions, analytics, test ids and copy all stay exactly as they were.
- Move what stays to tokens. Replace hard-coded colors and radii in the domain components with the map below, so they follow the themes too.
- Tick it off. Update
koala-migration.md. When a step fails its checks, fix it or stop and say so: never skip a test to move on.
Step 6: Move the structure#
With the pieces in place, move each screen’s shell, one at a time. What the structure is follows from the parts you drop in. Layout and its Navbar and Sidebar are Pro. Without a license, build the free column with Tailwind between Koala components.
| In your project | With Layout (Pro) | With the free components |
|---|---|---|
| Top navigation over the content | LayoutTopbar with Navbar, then LayoutContent | A header with Link or Button, and Tabs for the sections. |
| Side navigation | LayoutSidebar with Sidebar, then LayoutContent | A flex row with a ScrollArea column of Buttons. |
| Top bar over a side rail | LayoutTopbar, LayoutBody, LayoutSidebar | The two above, nested. |
| Icon rail plus a second column | LayoutRail, LayoutSidebar | Two fixed columns, with Tooltip on the icons. |
| Columns side by side: inbox, list and detail, point of sale | LayoutPane and LayoutPaneHeader | A CSS grid with one ScrollArea per column. |
| Columns the user can resize | Resizable | Fixed widths. |
| Page title with actions | PageHeader, PageHeaderActions | A flex row with a heading and Buttons. |
| Sign-in or onboarding split | SplitLayout, SplitPane, SplitMedia | A two-column grid. |
| Phone navigation | LayoutMobileBar, LayoutMobileSidebar | Drawer behind a menu Button. |
- Keep the routes. Every link, active state, permission check and badge count stays. Whatever a screen showed in its navigation, it still shows.
- Set the density once.
compactfor an app shell,comfortablefor a marketing page, with aDensityProvideraround the subtree. See Density. - Size for the hand. Density tunes spacing, not control height. On a touch screen, pass
size="lg"to the controls people hit all day. - Keep what scrolls. A column that scrolled alone still does, and the action that was always in view stays in view. Check it at the smallest screen the product supports.
Component map#
What most projects have, whatever it is called there. Every Koala name links to its page, and the skill holds the same page as a reference for each free component.
| In your project | Koala UI | Watch for |
|---|---|---|
| Buttons, icon buttons | Button | Pick variant="primary" once per screen. The default size is md. |
| Text links | Link | default for chrome, prose inside sentences. |
| Text fields, labels, errors | Input, Field | Keep the form library. Field wires the ids and the error. |
| Checkboxes, radios, switches | Checkbox, Radio Group, Switch | onCheckedChange or onValueChange receive the value, not an event. |
| Selects | Select, Native Select, Combobox | Native Select when the OS list is better, Combobox when the list needs filtering. |
| Modals | Dialog | Delete hand-rolled focus traps and scroll locks. Radix owns them now. |
| Side panels | Drawer | Blocks the page. Use Sheet for one that stays open over a canvas. |
| Menus | Dropdown Menu, Context Menu | Keep each item's handler as it is. |
| Tooltips, popovers | Tooltip, Popover | Hints in Tooltip, anything interactive in Popover. |
| Toasts, snackbars | Toast | One provider near the root, then one call per message. |
| Banners, inline errors | Alert | Five semantic variants. |
| Tabs, accordions | Tabs, Accordion | Keep URL-synced tabs controlled. |
| Status tags | Badge | Soft tints from the status roles. |
| Spinners, skeletons | Spinner, Skeleton | A loading button takes loading, not a spinner child. |
| Tables, data grids | Data Table | Pro. Keep your query and sorting logic. |
| Date pickers | Date Picker | Pro. |
| Charts | Chart | Pro. Keep the old library if the charts are not part of the plan. |
Token map#
| Hard-coded today | Koala token | Notes |
|---|---|---|
| Brand color | bg-brand, text-brand, --brand | One value recolors every brand surface. |
| Page background | bg-background | bg-canvas for the back layer of an app shell. |
| Card or panel fill | bg-card, bg-popover | Only on surfaces that are raised. In-flow containers paint no fill. |
| Main text | text-foreground | Headings and labels. |
| Long reading copy | text-body | Paragraphs. |
| Secondary text | text-muted-foreground | Meta and captions. |
| Borders | border-border, border-input | border-input around controls. |
| Error, success, warning, info | destructive, success, warning, info | A soft tint is bg-success/10 text-success-strong. |
| Border radius | rounded-sm to rounded-2xl | All driven by --radius. |
| Shadows | shadow-xs to shadow-xl | Tuned per theme. |
| Transitions | duration-fast, duration-base, ease-out | Name the property: transition-colors, never transition-all. |
Keeping both working#
- Unlayered CSS wins. Tailwind’s utilities live in a cascade layer, so a global
button { },a { }or* { }rule in your old stylesheet beats every Koala class. Scope those rules to the old components, or move them into@layer base. - Tailwind resets the page. On a project that had no Tailwind, its base styles strip the default look from headings, lists and links. Check the old pages right after adding it.
- Token names can collide.
initwarns about your own--background,--foregroundand--radius. Rename yours or remove them, or they override Koala’s themes. - A kit’s theme provider stays until its last component is gone. It can wrap the same tree as Koala’s
ThemeProvider.
Step 7: Clean up#
Once nothing imports them, delete components/legacy/, the old stylesheets and the packages Koala replaced, then run the checks and the production build one last time. Keep koala.json in git: diff and update use it to bring in fixes without touching the files you edited.