Components
Turn any box into a reusable component. The box you started from becomes the definition — it stays exactly where it is on the canvas, wearing the hollow brick mark — and every copy you place from the library is an instance, wearing the filled diamond cluster: a real subtree of ordinary boxes and text that remembers where it came from.
Centina's components are deliberately honest about what they are. An instance is not a locked proxy of the definition — restyle it, rewrite its text, move its pieces, and every edit records as a customization that survives updates. What an instance doesn't own is its structure: layers can't be added, removed, or reordered inside one — that shape is the definition's, and keeping it stable is what lets Centina tell you when an instance has drifted, replay your customizations after an update, and pull the definition's latest content back in when you ask.
Creating a component
Select a box and choose Create component from the right-click menu, or press ⌥⌘K. The box is promoted in place:
- its label and selection outline turn the component purple, with the brick glyph (the hollow brick is the definition; instances wear the filled diamond cluster);
- it enters the canvas's component library — the brick button at the top of the left rail, next to Present's play button. Click it and the library slides in over Folios and Layers; click again (or press Escape) and it slides away, your place in the layer tree untouched;
- the component records its birth context: which page and folio it was born on, what contained it, when, and how big it was.
A small chip on the brick button carrying the purple sync glyph (a double-arrow cycle) means some instance is behind its definition — open the library for the per-component detail. The glyph means one thing everywhere it appears: behind — a sync is available here. The library is scoped to its canvas: components travel between canvases only when you copy/paste them (see "The provenance trail").
Only boxes become components (wrap anything else in a box first — ⌘B), and a box that's already a definition or an instance can't be reified again.
Placing instances
The library shows each component as a card: a search field on top, and under it thumbnails that are the definitions themselves — real DOM, scaled live, never a raster (a component whose definition lives on another page, or only as a snapshot, shows the hollow brick instead).
- Drag a card out of the Components panel onto the canvas. The instance lands where you drop it — inside the box under your cursor, or on the canvas — and follows your pointer as a small chip while you drag. Escape cancels.
- Right-click a card → Place instance drops one near what you're working on, never on top of existing work.
- ⌘D on a definition stamps an instance next to it — duplicating a definition never creates a second definition.
Instances carry the filled diamond cluster. They're named after the component but the name is yours to change; renaming an instance never touches the library.
One rule is enforced: a component can't be placed inside its own definition (directly, or through another component that contains it). That drop is refused with a toast.
Editing, and staying in sync
Edit an instance's properties like anything else — move, resize, restyle, rewrite text. Edit the definition the same way, structure included. When an instance's content no longer matches its definition, Centina shows it:
- the purple sync glyph on the library card (hover it for the count), on the instance's canvas label, and on its row in Layers (which is how a nested instance with no label of its own still shows it);
- an Out of sync line in the inspector's Component card, with Bring into sync underneath it.
Position, size, rotation, name, visibility, and margin are the instance's own — changing those never counts as drift, and updating never touches them.
The instance card speaks Figma. At the top of the inspector, the component's name is the swap menu (click it), and the icon row beside it carries the three verbs: push changes to the definition (the diamond with the up arrow), reset all changes (the return arrow), and ⋯ — Go to definition (or Restore definition, when only a snapshot survives) and Detach instance. Under the name, From this canvas says where the component was born; an imported component names its source canvas instead.
Structure is sealed. Inside an instance you can't add, remove, or reorder layers, and the constraint anchors of interior layers can't change — that shape belongs to the definition. The editor simply doesn't offer it: drawing over an instance lands beside it, drops and pastes retarget to the nearest box outside it, the Layers rows don't drag, the menu items gray out, and the constraints row explains itself. Deleting or duplicating layers inside an instance shows a toast with the two ways out: edit the definition (instances follow when you bring them into sync), or detach (⌥⌘B) to turn the copy into plain boxes — and if you detach by accident, ⌘Z restores the link. The instance root itself stays an ordinary layer: move it, delete it, duplicate it, reorder it among its siblings.
Hiding a layer is the escape valve inside the seal: visibility is a property, so unchecking the eye on an instance's layer records as a customization — the closest thing to "removing" a piece of one copy.
Content edits on an instance become customizations. Change an instance's text, colors, or layout details and Centina records what you changed (the inspector reads "Customized — in sync"). Customizations are a fact, not a warning — they never raise the sync glyph, and they survive updates: when the definition moves on and you bring the instance into sync, the new content comes in and your customizations replay on top. A customization on a field hides definition changes to that same field — yours wins until you discard it.
The customization list. When an instance carries customizations, the sync line becomes a disclosure: open it and every property this instance sets differently from the definition is a row — which element, which property, your value — with a hover × to reset just that one. A grayed row with a dashed underline means the definition no longer has that property at all; the record does nothing and is pruned on the next update. The two whole-instance verbs live as the header icons, each direction spelled out in its tooltip:
- Reset all changes — this copy goes back to the definition's values.
- Push changes to definition — the definition adopts these values instead. A confirm spells out the consequence: this instance stops customizing them, and every other instance reads out of sync until you update it — pushing never silently changes the other copies. Pushing needs a live definition that isn't mid-face-edit, and an instance showing the base face; a customization inside a nested component is skipped with a note — open that component and edit it directly.
Bring into sync pulls the definition's current content into an instance: from the instance (inspector or right-click), from the definition (Update all instances), or from the library card. It replaces the instance's children and adopts the definition's layout behavior and styling, keeps the instance's geometry, name, selected face, and customizations, and is one undo step per page.
Tags — and states
Components carry freeform tags — "button", "marketing", "wip", whatever fits. Add them on the definition card (the tag chips with the little tag icon; type a name, Enter); they show on instances, on library cards, and they travel with the component through copy/paste.
Plain tags are deliberately inert in the editor — no behavior hangs off them. Their audience is agents: MCP surfaces tags on every component (get_canvas_info.components) and on any linked node (get_node_info), so an agent can judge what a component is before touching it. They're the first slice of the coming context feature.
A leading colon speaks to states instead. A state like :hover isn't a label — it's a variant face: the component's full content under that name. Type : in the tag input and the menu comes pre-populated with the standard interaction states — :hover, :pressed, :focus, the ones that compile to real CSS pseudo-classes — plus any states this component already has, filtered as you type. You never create :hover from scratch: pick it (Enter takes the highlighted row, arrows and click work too) and it's created and mounted for editing in one move; picking a state that already exists just re-enters it. A name of your own — :dark, :empty — still works: Enter creates it as a freeform variant (its face starts as a copy of the base). State chips carry the component purple (brick, colon, purple text) so they never read as plain tags — and because states are exclusive, at most one chip is ever filled: click a chip to edit that face on the canvas — it fills solid and the box label names the mounted face — click it again to save and return to the base. Double-click a chip to rename the face (every instance showing it follows along); hovering reveals × to delete it — deleting a face that instances are showing asks first, and those instances keep their look and read out of sync until updated. Chip tooltips whisper how many instances show each face.
Names compile to CSS by identity: hover / pressed / focus become real pseudo-classes, and a **name=value name groups into an axis** — size=sm and size=lg read as one family (the size= prefix mutes on the chips) and compile to [data-size="sm"] / [data-size="lg"] attributes on export. Anything else becomes a data-variant attribute.
Instances pick faces too. The same chips appear on an instance's card, live: click one and this copy shows that face — the chip fills, the canvas label wears the face's name, and your customizations stay on top. Click the filled chip to return to the base (there is no "base" chip — base is the absence of a selection, exactly as on the definition). One copy can be size=lg while another shows hover and a third stays on the base; sync is judged through each instance's selected face, so showing a face is never "out of sync." If a selected face is later deleted, nothing snaps: the chip renders dashed, the instance reads out of sync, and Bring into sync returns it to the base.
Swapping instances
Placed a Card where you want a Button? Click the component's name at the top of the instance's inspector card (the faint chevron beside it is the tell) and pick a replacement — the popover lists the library with live previews, grows a filter once the library is big enough, and keeps one promise: customizations that still apply carry over. The instance keeps its position, size, and (if you renamed it) its name; a selected face maps to the new component when it has one by the same name; and when some customizations can't carry (the new component has no matching element), a toast says exactly how many made it. Also available as right-click → Swap instance…, and from the library: hold ⌥ while dragging a row and drop it on an instance — the drag chip flips to "Swap to …" and the target outlines in purple so the aim is never ambiguous.
Where it's implemented
Agents can stamp implementation links onto a component over MCP (link_component_implementation): a label, URL, repo path, commit. They show under Implementation on the definition card (click to open, hover to remove) and in the event log — the design→code half of the provenance graph. An agent that scaffolds code from get_component records where it put it in the same breath.
Finding your way around
- Go to definition (instance right-click, or ⋯ on the instance card) selects and frames the definition — and for as long as it stays selected, Escape returns you to the instance you came from (the definition's inspector card shows the same "Return to instance" chip). Click anywhere else and Escape means escape again.
- Select instances (definition right-click or library card) selects every instance on the current page and frames them; when this page has none, it jumps to the busiest page that does. The toast counts anything beyond the frame.
- Clicking a library card reveals the definition. Cards never mutate on click — restoring, placing, updating all live behind explicit verbs.
The provenance trail
Every component keeps its history — created, placed, detached, updated, renamed, imported, restored, each with where and when — but the inspector stays quiet about it by design: the log lives in the model and agents read it over MCP (get_component_info), alongside the immutable birth certificate (page, container, time, size).
Definitions travel with copy/paste. Paste a component (or anything containing instances of it) into another canvas and the definition comes along — same identity, origin intact, an "Imported" entry in its log. Paste when the target canvas already has that component and the copies simply link up to it. Cut/paste moves a definition, within a canvas or between canvases.
Deleting things
- Deleting a definition (or its page) doesn't kill the component — the library keeps a snapshot, the card says "snapshot", and placing new instances keeps working. Restore definition (library card right-click, or ⋯ on any of its instances' cards) brings it back near your view; pasting the definition back revives it too.
- Detach (⌥⌘B, inspector, or right-click) unlinks an instance — the boxes stay exactly as they are, only the link goes.
- Delete component (library card, confirmed) removes it from the library: the definition becomes a plain box and every instance detaches. One undo step brings all of it back.
Interaction reference
| Action | How | Undo/cancel |
|---|---|---|
| Open / close the library | the brick button atop the left rail | Escape closes; display only, nothing to undo |
| Search the library | the field atop the panel — matches names and tags | display only; Escape clears the filter first |
| Create component | right-click → Create component, or ⌥⌘K | one undo step (sidecar + library row together) |
| Create components (bulk) | select several boxes → ⌥⌘K | one undo step — one component per box |
| Place instance (drag) | drag a library card onto the canvas | Escape cancels the drag; drop is one undo step |
| Place instance (menu) | card right-click → Place instance | one undo step |
| Stamp from definition | ⌘D on a definition | one undo step; the copy is an instance |
| Customize an instance | edit its content like anything else | one undo step; removes the edit AND its record together |
| Add/remove/reorder inside an instance | sealed — draws & drops land beside the instance, menus gray out, ⌫/⌘D toast the ways out | nothing happens, nothing to undo |
| Change interior constraint anchors | sealed — the constraints row grays with the hint | nothing happens; the instance root's own anchors stay editable |
| Hide a layer in one instance | the eye on its Layers row | one undo step; records as a customization (the seal's escape valve) |
| Bring into sync | instance inspector or right-click | one undo step; customizations replay on top |
| Update all instances | definition/row → Update N instances | one undo step per page touched |
| Reset one customization | open the customization list → hover a row → × | one undo step; that property returns to the definition's value |
| Reset all changes | the return-arrow icon in the card head | one undo step; back to the pure definition (a selected face stays) |
| Push changes to definition | the push icon in the card head → confirm | one undo step; siblings read out of sync — nothing auto-updates |
| Show a face on an instance | click a face chip on the instance card | one undo step (Show hover face); click the filled chip for base |
| Swap an instance | click the card's name · right-click → Swap instance… · ⌥-drag a library card onto it | one undo step; carrying customizations replay |
| Add a tag | definition card → tag chip +, type, Enter | one undo step |
| Remove a tag | hover the chip → × | one undo step |
| Add a state | tag input → :, pick a standard one (or type a custom name — size=lg makes an axis), Enter | one undo step — state created and its face mounted |
| Rename a state | double-click its chip on the definition card | one undo step; instance selections follow |
| Enter / switch a state | click its chip, or type : and pick from the list | one undo step; canvas swap and state table move together |
| Save & leave a state | click the filled chip | one undo step |
| Delete a state | hover its chip → × (confirmed when instances show it) | one undo step (a mounted face exits first; showing instances keep their look, read out of sync) |
| Rename component | rename the definition box, or card → Rename | one undo step (box + library together) |
| Detach instance(s) | ⌥⌘B, the card's ⋯, or right-click | one undo step; nested instances keep their links |
| Delete definition box | ⌫ on the definition | one undo step; snapshot captured, undo restores the live definition |
| Delete component | card right-click → Delete component (confirmed) | one undo step; instances re-link, definition re-marks |
| Restore definition | snapshot card right-click, or the instance card's ⋯ | one undo step (definition + snapshot-clear together) |
| Reveal definition | click a library card, or the instance card's ⋯ → Go to definition | camera move only — nothing to undo |
| Return to instance | Escape (or the inspector chip) after Go to definition | camera move only; stale after any other selection |
| Select instances | definition right-click, or library card | selection + camera only — nothing to undo |
| Card thumbnails | always live — the definition's real DOM, scaled | display only; the hollow brick when the DOM isn't reachable |
Agents and the library
Agents hold the full surface, seal included: the structural tools (write_html, create_box, move_nodes, delete_nodes, duplicate_nodes) refuse below an instance root with an error naming the instance, the definition, and the ways out. The loop: create_component reifies a box (tags at birth), place_instances places linked copies (optionally born on a face), the ordinary tools do the customizing — edit an instance with update_styles or set_text_content and the change self-records as a customization — get_node_info reads the records, and update_instances carries the verbs: a bare {id} is Bring into sync, plus variant, swapTo, pushOverrides/resetOverrides (by the records' address+path), and detach. update_components patches the library side (rename, tags, faces — including mounting a face for editing: the agent edits the mounted definition with the ordinary tools and exits with mountState: null; nothing reads as drift mid-mount, but good etiquette is never leaving the human in the mode). get_component_info is the deep read: origin, the event log, faces with the CSS each compiles to, and every instance site with its sync status. link_component_implementation still stamps the design→code half of the graph.
Known limits (by design, for now)
- Definition edits don't propagate automatically — updating is always an explicit, undoable act. The sync glyph is the nudge.
- Customizations track property edits; structural edits are sealed rather than tracked (see above). Customizations inside a nested instance don't survive the outer instance's update — the outer content is replaced wholesale.
- Plain tags carry no behavior by design — they're context for agents (and for you). States are the deliberate exception, and the leading colon is what marks a chip as one.
- Editing a state's face is a mode: the base parks while the face is mounted, and everything that means "the component's content" (updates, sync glyphs, placement) keeps reading the base. The filled chip and the box label are the reminders; click the chip again to come back.
- Pushing to the definition needs the instance on its base face — a face-selected copy would push base-shaped values at face content.
- Deleting a page that holds a definition captures its snapshot outside the undo stream — page deletes themselves aren't undoable.