Work / Trunk / Wiki / Decisions
2026-10-21-components-like-figma
Decisioncanonicalverified 2026-10-21
DECISION.2026-10-21.COMPONENTS-LIKE-FIGMAComponents are made like Figma: sets, properties, interactions, Trunk Kit, a playground
Screens approved by the owner on 2026-10-21. The schema is 2026-10-21-schema-0024, approved 2026-10-21. Feature: features/0024-figma-like-components.md. Second round, 2026-10-21: the owner turned down the first (a New component with eight starting tiles) because the list was far too small and Figma does not work that way.
Why
The owner asked for Text and Rectangle in Components › Edit, and for making components to work like Figma: components such as Button, Switch, Checkbox and RadioGroup, each previewable in Empty · Loading · Data · Error, with their actions.
Today a component is a list of variants, each with a fixed state and size and a style name. Drawn layers (frame, rectangle, text, path, a component's use) can be drawn on a variant. Links with a LinkAction exist only between pages. Nothing says "this text is the label" or "a click turns it on", so Get code writes no props or handlers, and nothing can be tried.
How Figma does it:
- A component set holds variants; each variant is one combination of variant properties.
- Component properties: Variant, Boolean (shows or hides a layer), Text, Instance swap (a nested instance, with a default and preferred options), Slot (an area for any content).
- Interactive components: prototype interactions on a variant, with a trigger (On click, While hovering, While pressing, After delay, On key) and the action Change to, which moves to another variant of the same set. A nested instance can change its parent's variant.
- A new component always starts blank. Ready-made components come from whole UI kits in the Assets tab: Figma's Simple Design System, Google's Material 3 Design Kit and Apple's iOS and iPadOS kit. Each is a full library: you drag instances from it, use them as they are or detach them. When a kit changes, Figma offers the update and you accept it. Community and team libraries work the same way.
Decision
1. A component is a set of variants laid out as a grid
- State and Size are fixed variant properties of every component. The author adds their own: Checked (off · on · mixed), Open (no · yes), Expanded, Type, Value, Width, Selected.
- Each combination is a variant, drawn in one dashed frame. State runs down; the author's properties run across. A set can be turned (Button, Toast, Modal).
- Size is the viewport picked in the toolbar, never a grid axis. The toolbar's state pick gains All states (the grid).
- A state the component does not make is not drawn; the set says "not made", and its preview shows Data.
- A cell that does not depend on a property spans it (Select's Empty spans Open).
- Interaction states (Hover · Focus · Pressed · Disabled) are a built-in property: no wiring; Get code writes
:hover,:focus-visible,:active,:disabled.
2. Component properties
Text · Boolean · Instance swap · Slot, plus Variant. Each has a name, a type, a default and, for Variant and Instance swap, options. Get code writes each as a prop.
3. Tools, and binding a layer to a property
- Select V · Frame F · Rectangle R · Pen P · Text T · Icon I · Component C · Hand H, the same on a page and on a component. Icon places an icon from Styles › Icons. Auto layout on any frame (⇧A).
- A layer is bound from its own field with ◇: a text to a Text property, any layer's visibility to a Boolean, a nested icon or instance to an Instance swap, a frame's content to a Slot.
4. Interactions
The inspector gains an Interactions tab in Edit. An interaction is: on a layer or the whole variant, runs in some variants, then trigger → actions in order.
- Triggers: On click · While hovering · While pressing · On key · After delay · When a part emits (a nested instance emits an event).
- Actions on a component: Change to (another variant of this component, or of the parent from a nested instance) · Set property (a value, or toggle) · Show component · Close component · Emit event (named, with a property's value; Get code writes it as a handler prop).
- Open page lives only on the instance, in Pages, with a
LinkAction(Tap, Submit, After sign-in, Redirect, Back): where a Button goes differs on every page. An instance can also Show a component there (Upload Form 16 shows the Toast). - A nested instance can change its parent: the Old regime radio changes RadioGroup to Value · old. The part's own interaction for that trigger is shown as replaced.
- Page Preview plays component interactions too: a Switch on
/taxturns on, a Select opens, a Toast closes after 5 s.
5. The playground
⌘P on a component opens its playground in a new tab: the component live, a State switch (Empty · Loading · Data · Error), the viewport, a control per property, and a log of every emitted event. Share makes a preview link, as for a page.
6. Start from: Blank, From the kit, or Duplicate
- Blank is the Figma way: one root frame with auto layout, which the component's Fill, Text, Border and Radius style.
- From the kit opens the catalogue of Trunk Kit: groups down the side, search, every component as a small preview, and the picked one live in the product's Styles with its states, properties and interactions. Add to draft.
- Duplicate copies a component of the product.
7. Trunk Kit
- Trunk's own full catalogue, like Figma's Simple Design System: 66 components in eight groups, every one in release 1.0.0. The list, with each component's variant properties, component properties, interactions and events, and real states, is the entity page trunk-kit:
- Actions (6): Button, Icon button, Button group, Link, Split button, FAB
- Inputs (18): Text field, Textarea, Number, Password, Search, Select, Dropdown menu, Combobox, Multi-select, Date picker, Time picker, Date range, Slider, Range slider, File upload, OTP / PIN, Color picker, Rating
- Selection (8): Checkbox, CheckboxGroup, Radio, RadioGroup, Switch, Segmented control, Chip / Tag, Toggle
- Navigation (9): Tabs, Breadcrumb, Pagination, Stepper, Sidebar nav, Top bar, Bottom nav, Menu, Command palette
- Feedback (7): Toast, Alert / Banner, Progress bar, Spinner, Skeleton, Empty state, Badge
- Overlays (6): Modal / Dialog, Drawer / Sheet, Popover, Tooltip, Confirm dialog, Context menu
- Data display (9): Card, Table, List, Avatar, Avatar group, Accordion, Stat / KPI tile, Timeline, Tree view
- Form layout (3): Form field, Fieldset, Inline validation
- Painted from the product's Styles. The kit names roles (fill, on-fill, line, radius, text, motion, icon) and Trunk fills them with the product's colors, fonts, type, radius, spacing, shadows, motion and icons. Nothing is added to Styles.
- Every component has its four states, its properties, its interactions and events, and a playground.
- A component taken from the kit stays linked to it. The Kit row at the top of the Components panel opens the Kit screen: the version in use, what is used from it, and, when Trunk Kit improves, the update, offered into the draft for Review (like Figma's library updates). A component's own edits stay; where both changed one property it is a conflict on Review.
- Detach cuts the link. A detached component gets no updates, and the Kit screen says so.
8. Import a kit
Components can be imported from the same systems as Styles › Import a system: Tailwind UI, Material UI, shadcn/ui, Chakra UI, Radix Primitives, Bootstrap, Material Design 3, or an upload (a Figma library, a Storybook). Their tokens are mapped onto the product's Styles, and the mapping and a preview are shown before anything comes in. A component the product already has is "keep yours" by default. The import goes to the draft. An imported component remembers where it came from and gets no Trunk Kit updates.
9. Answers to the first round's questions
- Open page lives only on the instance in Pages.
- Page Preview plays component interactions.
- Captured variants are not turned into properties in this round. A one-click suggestion comes later.
- Toast's tone Error is renamed Danger, so it never reads as the State.
- "When a part emits" stays in 1.0.0.
10. Answers the owner accepted with the screens (2026-10-21)
- A detached component can be reset to the kit. Reset to kit drops its own changes and takes the kit's values again, in the draft, like any edit.
- One version number for the whole kit (1.0, 1.1). Each component records the release it was taken at or last updated to, because an update can leave one out.
- A duplicate of a kit component starts detached. It keeps its kit link, so it can be reset to the kit.
- A Figma library is imported from an uploaded file first. No Figma token is asked for.
Everything goes to the draft, as every edit does.
How it maps to Figma
| Figma | Trunk |
|---|---|
| Component set | A component, its variants drawn as a grid in one dashed frame |
| Variant property | Variant property; State and Size built in |
| Boolean, Text, Instance swap, Slot | The same four, bound from the layer's field |
| Interactive component: trigger → Change to | Interactions tab: trigger → Change to, Set property, Show, Close, Emit |
| Nested instance changes its parent | The same, with the replaced part interaction shown |
| Prototype / present | The playground, and page Preview |
| New component: blank | Blank |
| Assets › UI kits (Simple Design System, Material 3, iOS) | Trunk Kit, painted from your Styles |
| Library update: review and accept | Kit update, offered into the draft for Review |
| Detach instance | Detach from the kit: no more updates |
| Community and team libraries | Import a kit, mapped onto your Styles |
Where it differs from Figma, and why
- State is built in (Empty · Loading · Data · Error): the product is captured and verified in those states.
- Size is the viewport, not a variant: every set would triple.
- Set property with toggle: one action instead of a Change to per variant.
- Interaction states are a property that becomes CSS; in Figma they are hand-wired variants the code never learns about.
- Emit event is an action, and becomes a handler prop and a log line; Figma's interactions never reach code.
- Show / Close component name a component; Figma uses overlays of frames.
- When a part emits lets a group react to its parts.
- Open page is on the instance only: a component is reused on many pages.
- The kit is painted from your Styles. A Figma kit arrives in its own look and is restyled by hand; Trunk Kit has no look of its own.
- A kit update goes to the draft and Review, so it is versioned, synced and verified like any change, instead of being accepted file-wide.
- Import maps onto your Styles first and shows the mapping; a Figma library brings its own styles alongside yours.
- Cells span what does not depend on a property.
Schema it will need
Written in 2026-10-21-schema-0024, which differs from this sketch where a row would have had to change (property names and order, a captured variant's values, an interaction's actions): those are changes. The sketch as first described: Values that change stay changes on a ref, so drafts, branches, sync, conflicts and merge work unchanged. Rows hold only what never changes.
ComponentProperty:componentId,name,type(VARIANT·TEXT·BOOLEAN·INSTANCE_SWAP·SLOT·INTERACTION_STATES),codeName,position. Its default, options and label are changes (targetKind COMPONENT_PROPERTY). State and Size stayVariant.stateandVariant.size.VariantPropertyValue:variantId,propertyId,value; unique per (variant, property). No row means the variant spans that property.Variant.namebecomes derived.- Layer binding: a change on the DRAWN_ELEMENT (
bind.text,bind.visible,bind.instance,bind.content= a property id), so it is versioned like any edit. Interaction:componentId, the layer (a drawn element, a selector, or none),trigger(CLICK·HOVER·PRESS·KEY·DELAY·PART_EVENT),position. Key, delay, the variants it runs in and its actions are changes (targetKind INTERACTION).InteractionAction:interactionId,position,kind(CHANGE_TO·SET_PROPERTY·SHOW_COMPONENT·CLOSE_COMPONENT·EMIT),target(SELF·PARENT), and its arguments as one JSON value checked in@trunk/db.- Instance interactions on a page: the existing journey link (
LinkAction) stays the Open page; Show component on an instance is a new action on a PAGE_ELEMENT or DRAWN_ELEMENT. - Events are derived from the Emit actions; no table.
- Where a component came from, on
Component:source(CAPTURED·CREATED·KIT·IMPORTED),kitKey(the kit component, such astrunk/select),kitVersion(the version it was taken at or last updated to), andimportSystem(shadcn/ui, MUI…) for an import.detachedis a change, so detaching is versioned and can be undone.PrimitiveOriginmay absorbsource. KitReleaseis not a company table: Trunk Kit and its versions ship in code (a package of component specs and release notes). An update offer is computed: kit components whosekitVersionis behind and that are not detached.TokenMappingfor an import: the system's token → the product's token, kept on the import's commit so the mapping can be read later.DrawnKindgainsICON.TargetKindgainsCOMPONENT_PROPERTYandINTERACTION.- Codegen reads properties, bindings and Emit actions into the component spec, so every converter writes props and handlers.
Impact
- Get code writes a real API; the CLI and MCP become useful to a developer.
- The grid replaces the flat variant list; View shows the set too.
- Trunk Kit is a large body of work: 66 component specs, each with its states, properties and interactions, painted by role. It is the biggest part of 0024.
- Verify and capture keep using State and Size.
Status
Screens approved 2026-10-21 (second round), with the answers in section 10. The schema approved 2026-10-21: 2026-10-21-schema-0024. Built in phases, one after another: 1, the component model and the editing canvas (properties, the set as a grid, one layer tree with each variant's own values, bindings, every drawing tool, Blank and Duplicate), built 2026-10-21, waiting for the owner; 2, interactions, the playground, Preview playing them and Get code's props, built 2026-10-21, waiting for the owner; 3, Trunk Kit, built 2026-10-22, waiting for the owner (editing a kit component keeps it linked: its own edits stay and clash on Review; Detach is explicit); 4, Import a kit, built 2026-10-22, waiting for the owner (the seven systems map onto Trunk Kit's components; an uploaded Figma file's REST JSON is read, no Storybook yet).
Related
- trunk-kit · 2026-10-21-schema-0024 · 2026-10-17-drawn-elements · 2026-10-17-get-code · 2026-10-18-editor-redesign · 2026-10-18-schema-0020
- component-sets · components-properties · components-interactions · components-playground · components-start-from · components-kit · components-kit-update · components-kit-import
Sources
- The owner's requests, 2026-10-21 (two rounds).
- Figma Help: variants and component sets; component properties; interactive components; the Assets panel and UI kits (Simple Design System, Material 3 Design Kit, iOS and iPadOS); library updates; detaching instances.
- .context/designs/web/components-sets/ · components-sets-display/ · components-kit/ · components-edit/ · components-play/