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 skill

Then 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 needIf you do not have it
React 19Upgrade React first, as its own change.
Tailwind CSS v4Upgrade from v3 first. On a project without Tailwind, add it, then read Keeping both working below.
The @/* path aliasAdd it to tsconfig.json, and to vite.config.ts in Vite. init checks it.
Next.js or Viteinit detects both. Anywhere else it prints the steps to wire by hand.
A clean branchCommit 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.mdText
# Koala migrationStack: Next.js 16, React 19, Tailwind 3, CSS ModulesBlockers: Tailwind 3 to 4## BrandPrimary #0f766e, radius 12px, Inter## StructurePoint of sale on a touch screen: density compact, controls size lgRegions: top nav, open tickets, product grid, ticket panelDecision: 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 issrc/components/CashCount.tsx: domain component, tokens only

Mark 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:

DecisionWhat it meansWhen
KeepSame screens, regions and order. Only the pieces that draw them change.The default, and the right call for tools people use all day.
ReorganizeListed 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 providernpx koalaui-cli@latest init# Only if the plan uses Pro componentsnpx koalaui-cli@latest login <your-key># The free components the plan needsnpx koalaui-cli@latest add button input field dialog

If 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:

globals.cssCSS
: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#

  1. 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.
  2. 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.
  3. Translate props, keep behavior. Handlers, validation, routes, disabled conditions, analytics, test ids and copy all stay exactly as they were.
  4. 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.
  5. 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 projectWith Layout (Pro)With the free components
Top navigation over the contentLayoutTopbar with Navbar, then LayoutContentA header with Link or Button, and Tabs for the sections.
Side navigationLayoutSidebar with Sidebar, then LayoutContentA flex row with a ScrollArea column of Buttons.
Top bar over a side railLayoutTopbar, LayoutBody, LayoutSidebarThe two above, nested.
Icon rail plus a second columnLayoutRail, LayoutSidebarTwo fixed columns, with Tooltip on the icons.
Columns side by side: inbox, list and detail, point of saleLayoutPane and LayoutPaneHeaderA CSS grid with one ScrollArea per column.
Columns the user can resizeResizableFixed widths.
Page title with actionsPageHeader, PageHeaderActionsA flex row with a heading and Buttons.
Sign-in or onboarding splitSplitLayout, SplitPane, SplitMediaA two-column grid.
Phone navigationLayoutMobileBar, LayoutMobileSidebarDrawer 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. compact for an app shell, comfortable for a marketing page, with a DensityProvider around 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 projectKoala UIWatch for
Buttons, icon buttonsButtonPick variant="primary" once per screen. The default size is md.
Text linksLinkdefault for chrome, prose inside sentences.
Text fields, labels, errorsInput, FieldKeep the form library. Field wires the ids and the error.
Checkboxes, radios, switchesCheckbox, Radio Group, SwitchonCheckedChange or onValueChange receive the value, not an event.
SelectsSelect, Native Select, ComboboxNative Select when the OS list is better, Combobox when the list needs filtering.
ModalsDialogDelete hand-rolled focus traps and scroll locks. Radix owns them now.
Side panelsDrawerBlocks the page. Use Sheet for one that stays open over a canvas.
MenusDropdown Menu, Context MenuKeep each item's handler as it is.
Tooltips, popoversTooltip, PopoverHints in Tooltip, anything interactive in Popover.
Toasts, snackbarsToastOne provider near the root, then one call per message.
Banners, inline errorsAlertFive semantic variants.
Tabs, accordionsTabs, AccordionKeep URL-synced tabs controlled.
Status tagsBadgeSoft tints from the status roles.
Spinners, skeletonsSpinner, SkeletonA loading button takes loading, not a spinner child.
Tables, data gridsData TablePro. Keep your query and sorting logic.
Date pickersDate PickerPro.
ChartsChartPro. Keep the old library if the charts are not part of the plan.

Token map#

Hard-coded todayKoala tokenNotes
Brand colorbg-brand, text-brand, --brandOne value recolors every brand surface.
Page backgroundbg-backgroundbg-canvas for the back layer of an app shell.
Card or panel fillbg-card, bg-popoverOnly on surfaces that are raised. In-flow containers paint no fill.
Main texttext-foregroundHeadings and labels.
Long reading copytext-bodyParagraphs.
Secondary texttext-muted-foregroundMeta and captions.
Bordersborder-border, border-inputborder-input around controls.
Error, success, warning, infodestructive, success, warning, infoA soft tint is bg-success/10 text-success-strong.
Border radiusrounded-sm to rounded-2xlAll driven by --radius.
Shadowsshadow-xs to shadow-xlTuned per theme.
Transitionsduration-fast, duration-base, ease-outName 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. init warns about your own --background, --foreground and --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.

FAQ#

No. Stop after any surface: each one is finished and working on its own. Many teams move the shared components and leave domain components on tokens.