Skip to main content
Subframe now syncs each component as its own directory instead of a single file. This page explains what changed, why, and how to migrate an existing project.

What changed

Previously, each component synced as a single flat file:
Now each component (and page layout) syncs as a directory:
Button.tsx is the component Subframe generates, exactly as before. index.tsx is a thin wrapper that re-exports it:
index.tsx
Your imports don’t change. @/ui/components/Button resolves to the directory’s index.tsx, so existing code keeps working.

Why

A per-component directory gives each component a home for everything that should live alongside it in code:
  • index.tsx — a natural, stable place for your own wrapping logic and wrapper components, kept separate from the source Subframe generates.
  • Button.md — component documentation describing what the component is and how it should be used.
  • In the future — generated .stories files for Storybook, test files, and more.
It also makes disabling sync granular. @subframe/sync-disable works per file, so you can freeze your index.tsx while Button.tsx keeps receiving Subframe’s visual updates — instead of having to freeze the whole component.

Adding business logic

index.tsx is where your code goes. To extend a component, edit its index.tsx and add the @subframe/sync-disable marker so the CLI won’t overwrite it on the next sync:
index.tsx
Button.tsx has no marker, so it keeps syncing — design changes from Subframe still flow in, while your logic in index.tsx is preserved. Anything importing @/ui/components/Button gets your wrapped version automatically, with no import changes.

Migrating an existing project

The CLI migrates your project automatically as you sync. There are no breaking changes — imports are unchanged, so a component moving into a directory doesn’t affect anything that imports it. The one exception is sync-disabled components, which need a quick import review (covered below). Because nothing breaks, you can migrate incrementally — sync a few components at a time, or run a full sync to do everything at once. Flat and nested components coexist fine while you’re partway through.
The migration runs in recent versions of the CLI. The commands below use @subframe/cli@latest so you always get the newest — if you’ve pinned an older @subframe/cli, update it before migrating.
1

Sync your components

Run a full sync to migrate everything at once:
Or sync specific components — npx @subframe/cli@latest sync Button Alert — to migrate just those. Either way, the CLI writes the new directory layout and removes the old flat files for the components it syncs.
2

Update monorepo exports (if applicable)

If you expose Subframe components from a shared package using the exports field in package.json, update the subpath patterns to point to the new directory layout:
package.json
The old "./ui/components/*.tsx" pattern no longer matches because the flat files have moved into per-component directories. See the monorepo guide for the full setup.
3

Review any sync-disabled files

If you’d added @subframe/sync-disable to a component file under the old layout, the CLI won’t delete it — instead it moves it into the new directory (e.g. components/Button.tsxcomponents/Button/Button.tsx) and prints a warning listing each moved file.Because the file moved one level deeper, two things need a quick check.Relative imports. Paths that were correct when the file was flat are now off by one level — siblings, shared root files, and cross-directory references all shift:
Exported prop types. The directory’s index.tsx re-exports the component (export const Button = ButtonComponent), so TypeScript has to be able to name the types in its signature. If a moved file declares its prop interfaces without export, you’ll get errors like Exported variable 'Button' has or is using name 'ButtonRootProps' … but cannot be named (TS4023). Add export to those interfaces:
Files the CLI regenerates already export their prop types, so this only affects files you’d frozen with @subframe/sync-disable.
4

Commit the change on its own

A full migration touches every component, so the diff is large but mechanical — committing it separately keeps it easy to review.
Last modified on May 27, 2026