> ## Documentation Index
> Fetch the complete documentation index at: https://docs.subframe.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Component directories

> Subframe now syncs each component as a directory. What changed, why, and how to migrate.

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:

```
src/ui/components/
└─ Button.tsx
```

Now each component (and page layout) syncs as a directory:

```
src/ui/components/
└─ Button/
   ├─ Button.tsx   // generated by Subframe — overwritten on every sync
   └─ index.tsx    // yours to edit — wraps and re-exports Button.tsx
```

`Button.tsx` is the component Subframe generates, exactly as before. `index.tsx` is a thin wrapper that re-exports it:

```tsx index.tsx theme={null}
"use client";
import { Button as ButtonComponent } from "./Button";

/**
 * Add wrapper components and business logic here.
 * If you modify this file, disable Subframe sync for it to prevent overwrites.
 * Learn more: https://docs.subframe.com/concepts/syncing-components#wrapping-components
 */

export const Button = ButtonComponent;
```

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](/concepts/syncing-components#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:

```tsx index.tsx theme={null}
// @subframe/sync-disable
import { Button as ButtonComponent } from "./Button";

export function Button({ onSubmit, ...props }) {
  const [loading, setLoading] = useState(false);

  async function handleClick() {
    setLoading(true);
    await onSubmit();
    setLoading(false);
  }

  return <ButtonComponent {...props} loading={loading} onClick={handleClick} />;
}
```

`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.

<Note>
  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.
</Note>

<Steps>
  <Step title="Sync your components">
    Run a full sync to migrate everything at once:

    <CodeGroup>
      ```bash npm theme={null}
      npx @subframe/cli@latest sync --all
      ```

      ```bash yarn theme={null}
      yarn dlx @subframe/cli@latest sync --all
      ```

      ```bash pnpm theme={null}
      pnpx @subframe/cli@latest sync --all
      ```

      ```bash bun theme={null}
      bunx @subframe/cli@latest sync --all
      ```
    </CodeGroup>

    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.
  </Step>

  <Step title="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:

    ```json package.json theme={null}
    {
      "exports": {
        "./components/*": "./ui/components/*/index.tsx",
        "./layouts/*": "./ui/layouts/*/index.tsx"
      }
    }
    ```

    The old `"./ui/components/*.tsx"` pattern no longer matches because the flat files have moved into per-component directories. See the [monorepo guide](/frameworks/monorepo) for the full setup.
  </Step>

  <Step title="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.tsx` → `components/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:

    ```tsx theme={null}
    // components/Button/Button.tsx
    import * as SubframeUtils from "../utils";    // ❌ correct when flat
    import * as SubframeUtils from "../../utils"; // ✅ shared root files

    import { Tooltip } from "./Tooltip";  // ❌
    import { Tooltip } from "../Tooltip"; // ✅ sibling components

    // layouts/DialogLayout/DialogLayout.tsx
    import { Dialog } from "../components/Dialog";    // ❌
    import { Dialog } from "../../components/Dialog"; // ✅
    ```

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

    ```tsx theme={null}
    interface ButtonRootProps extends ... {}        // ❌
    export interface ButtonRootProps extends ... {} // ✅
    ```

    Files the CLI regenerates already export their prop types, so this only affects files you'd frozen with `@subframe/sync-disable`.
  </Step>

  <Step title="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.
  </Step>
</Steps>
