---
title: Component sync
description: Define your React components in *.archetype.tsx files and push them to the brand, so Archetype holds what your code ships.
---

> Source: https://www.heyarchetype.com/docs/developers/components

Your repository's components are the real ones. Component sync draws them from your code and keeps them in the brand, so designers see what actually ships and agents are told to import your component instead of copying its markup.

## Before you start

- **React components.** Sync draws React components only, for now.
- **The CLI, set up.** Run `npx archetype-os init` first, so the project has an `archetype.json`. See [The CLI](/docs/developers/cli).
- **Three dev dependencies and a Chrome.** `components push` uses `esbuild`, `playwright-core` and Chrome: Google Chrome if it's installed, otherwise run `npx playwright install chromium`, or set `ARCHETYPE_CHROME` to a Chrome binary.
- **Edit rights on components.** Your role in the brand has to allow editing components.

```bash title="Terminal"
npm i -D archetype-os@0.3.2 esbuild playwright-core
```

## Define a component

Put one file beside each component, named `*.archetype.tsx` (`.jsx`, `.ts` and `.js` work too). It says what the component is, which properties it varies along, and how to draw each example:

```tsx title="components/ui/button.archetype.tsx"
import { defineComponent } from "archetype-os/components";
import { Button } from "./button";

export default defineComponent({
  key: "button",
  name: "Button",
  description: "The one button. Primary is only for Save on unsaved work.",
  code: { import: "@/components/ui/button", export: "Button" },
  properties: {
    Variant: { values: ["Secondary", "Primary", "Danger"], prop: "variant" },
    Size: { values: ["Medium", "Large"], prop: "size", map: { Medium: "md", Large: "lg" } },
    State: ["Default", "Hover", "Pressed", "Focus", "Disabled"],
  },
  render: ({ Variant, Size, State }) => (
    <Button
      variant={Variant.toLowerCase()}
      size={Size === "Large" ? "lg" : "md"}
      disabled={State === "Disabled"}
    >
      Save
    </Button>
  ),
  examples: { matrix: ["Variant", "State"], include: [{ Size: "Large" }] },
});
```

| Field | What it does |
| --- | --- |
| `key` | The handle agents and the app use. Lowercase words joined by dashes, like `date-picker`. Don't change it once published. |
| `name` | The name people see. Required. |
| `description` | What it is and when to use it. At most 400 characters. |
| `platforms` | Which surfaces it's for: `universal`, `app`, `mobile`, `website`, `marketing`, `email`. |
| `nested` | `true` for a component that is only ever a part of others. |
| `code` | Where the real component lives: `import` (the path) and `export` (the name). Agents get this import line. |
| `properties` | The axes it varies along, in order. A list of values, the first being the default, or `{ values, default, prop, map }`. `prop` names the component prop the property sets, and `map` gives the prop's value for each property value when they differ. |
| `render` | Draws one example from the property values it is given. Required. |
| `examples` | Which combinations to draw. `matrix` crosses the properties it names (the rest stay at their default), and `include` adds single combinations. Leave both out to draw each value of each property, one property at a time. |

A property named **State** is the interaction state. **Hover**, **Pressed**, **Active**, **Focus** and **Focus visible** are drawn for real, by forcing the pseudo-class on the component in the browser, so `render` never fakes them. Every other state, such as **Disabled**, **Loading** or **Error**, is drawn by `render` from the component's own props.

### Settings for harder components

| Field | What it does |
| --- | --- |
| `width` | Width of each example in px, for a component that fills its parent. |
| `height` | Height of the window an overlay (a root that is `position: fixed`) is drawn in. 600 unless set. |
| `viewport` | Width of the window examples are drawn in. 1440 unless set. |
| `path` | The address examples are drawn at, which is what `usePathname` and `useSearchParams` read, so a nav lights the right row. `/` unless set. |
| `stateTarget` | A selector inside the root that Hover, Pressed and Focus are forced on, when the interactive element isn't the root. |
| `parts` | Other components nested inside this one that can't mark themselves: a selector mapped to that component's key. |
| `frame` | A `className` for the box examples are drawn in, for a component that needs its page's variables or fonts. |
| `prefix` | The root's class in the captured CSS. The key unless set. |

`width`, `height` and `path` can also be a function of the property values. An entry in `include` can set `$name`, `$width`, `$height`, `$viewport`, `$frame` and `$path` for that one example.

If your components rely on global stylesheets, list them in `archetype.json`. `publicDir` names the folder your site serves at its root (`public` unless set), and `publicUrl` where that folder is online, so stored markup points there:

```json title="archetype.json"
{
  "url": "https://www.heyarchetype.com",
  "brand": "acme",
  "components": { "css": ["app/globals.css"], "publicUrl": "https://acme.com" }
}
```

## Push to the brand

:::steps
### Sign in with edits allowed
Run `npx archetype-os login --write` once. A plain sign-in is read-only, and the push is refused without edits.

### See what would change
Run `npx archetype-os components push --dry-run`. Archetype answers with what the push would add, change and remove, and changes nothing.

### Send it
Run `npx archetype-os components push`.
:::

The push bundles your definition files with your project's own setup: its tsconfig paths, its React and its PostCSS config, so Tailwind and CSS modules compile as they do in your build. It draws every example in Chrome and reads back the CSS that actually applies to each element. A portal aimed at `<body>`, such as a dialog's, lands in the example, drawn open.

The brand gets one change per push, which one **Undo change** in **History** takes back. A push applies straight away rather than waiting as a draft: it's code that was already reviewed. Components your repository no longer defines are removed, unless you pass `--keep` or push only some with `--only`.

`--out <file>` writes what would be sent to a file instead, with no sign-in. See [The CLI](/docs/developers/cli) for every flag.

## Push from CI

Run the push on every merge so the brand never falls behind the code:

1. In **Settings → Agents**, on the **API** tab, an admin creates a key with **Sync components from code** ticked. The sync is made as the person who created the key, and stops working if they can no longer edit components.
2. Add it to the repository as the secret `ARCHETYPE_TOKEN`.
3. Copy `/ci/archetype-components.yml` from your Archetype address into `.github/workflows/`. It installs your dependencies and runs the push on pushes to `main` that touch your components or styles.

## What shows up in Archetype

- **Synced from code** sits where **Edit** would be on the component's page, with the commit it came from. It links to the definition file at that commit, and its tooltip names the repository, the file and when it last synced.
- The component is locked in Archetype. Comments stay open, but edits happen in the repository and the next push brings them in. Agents can't change it with `update_brand` either.
- When only some of the brand's components came from code, the Components grid shows a filter with their count (for example **12 from code**), and **Show all** to go back.
- Agents reading the brand over MCP get your import line and, where properties map to props, each example's usage, such as `<Button variant="primary" />`.

## Limits

| What | Limit |
| --- | --- |
| Description | 400 characters. A longer one fails the whole push, even with `--only`. |
| Properties per component | 8 |
| Values per property | 40 |
| Examples per component | 240 |
| Example HTML / CSS | 120,000 / 200,000 characters |
| Component and example names | 80 characters |
| Components per brand | 200 |

Two definition files can't share a `key`, and every definition needs a `key`, a `name` and a `render` function.

## Next steps

:::cards
- [Components](/docs/brand/components): How components work in Archetype, and how agents use them.
- [MCP tools](/docs/agents/tools): What agents get when they read a component over MCP.
- [The CLI](/docs/developers/cli): Every command and flag.
:::
