← All guides
FIGMA INT · 16 MIN

Components and Variants Are a Contract, Not a Copy-Paste Shortcut

A component set's real product isn't its visual polish — it's the property panel: the fixed set of Boolean, instance-swap, text, and variant controls it exposes to whoever drops it on a frame. Skip designing that contract on purpose, and "reusable" is just a label pasted onto components that happen to share one visual origin story, with overrides drifting, variants losing structure, and every rebrand starting from zero.

Most explanations of Figma variants start with the dropdown — click the diamond icon, pick a state, watch the layer swap. That's the interaction, not the design decision. The decision that actually matters happened earlier, when someone decided which aspects of a button, a card, or a list item were allowed to change and which weren't — and that decision is what everyone downstream is quietly relying on: a teammate dragging an instance onto a frame, another component nesting this one three levels deep, a design engineer translating it into code without opening the file. Call it a contract — a fixed set of promises, phrased as properties, backed by a structure that has to hold still underneath them.

Copy-pasting a frame and renaming it "Button — Secondary" produces something that looks identical to a real component from ten feet away, and behaves nothing like one. An edit to the original never reaches the copy. An override made on one instance has no relationship to overrides made on the others. Nothing in the file tells anyone what's safe to change and what will quietly break the layout. That gap isn't cosmetic — it's the difference between a system a team can extend for years and one that gets rebuilt from scratch every time someone touches it.

01What the contract actually protects

A main component defines two things at once: the visual structure — every layer, its name, its position, its z-order — and the defaults for anything that will later become a property. A component set is just several main components grouped together and switched between as variants. An instance is neither of those; it's a reference back to one specific main component (or, inside a set, one specific variant) that can vary only along the properties that were deliberately exposed, plus a narrow band of instance-level overrides on top.

That narrowness is the whole point, not a limitation to work around. An instance genuinely cannot reorder its own layers, change their z-index, or reposition a layer that's governed by auto layout — those changes can only be made on the main component itself, or by detaching the instance and giving up its connection entirely. It's exactly this restriction that makes the rest of the system trustworthy: because an instance can't quietly restructure itself, an edit made once on the main component can propagate to every instance in the file with predictable results. Loosen that restriction and the propagation guarantee disappears with it.

The contract, restated plainly: a property panel is a promise about what's allowed to differ. Everything the panel doesn't expose is implicitly promised to stay the same across every instance, everywhere, until someone edits the main component. A component with no thoughtfully designed properties hasn't avoided making that promise — it's just making an empty one.

02The five property types

Figma's component properties come in a small, fixed vocabulary. Four have been stable for years; a fifth, Slot, was added more recently for cases where a component needs to accept free-form content rather than a fixed value. Knowing which type controls which kind of change — and which type can't — heads off most of the "why won't this property show up" confusion before it starts.

Figma component property types, what they expose, and what they're bound to
Type Controls Must be bound to
Boolean Layer visibility only — nothing else A layer (any layer, shown or hidden)
Instance swap Which component fills a nested slot An actual nested instance layer
Text Editable string content A text layer
Variant Which whole variant is shown The component set itself
Slot A free area for arbitrary child content A designated slot region

The "must be bound to" column is where most first attempts go wrong. An instance-swap property can only attach to a genuine nested instance — not a group, not a plain frame that merely looks like one — and a text property can only attach to a text layer that isn't itself buried inside a separate nested instance. Select the wrong kind of layer and Figma won't offer the property at all, which reads as a bug the first few times and is actually the type system doing its job.

03Boolean and instance-swap in practice

The most common misconception about Boolean properties is that they can toggle anything — swap a color, change a corner radius, resize a frame. They can't. A Boolean property does exactly one thing: show or hide the specific layer it's bound to. If a design calls for something that reads as "toggle the emphasis color," the actual mechanism is two layers with opposite fills, each bound to the same Boolean and set to opposite visibility — or, more often, that it's not a Boolean at all and belongs on a Variant axis instead. Reaching for Boolean by default and hitting this wall is usually the signal that the property being modeled isn't actually binary in the way it first looked.

Instance-swap properties are more powerful and more dangerous in the same breath. Because the property swaps an entire nested instance, not just its glyph or fill, the swap carries that nested component's whole footprint along with it — intrinsic width, height, internal padding, everything. Swapping a 16px icon slot for a 48px illustration slot doesn't just change what's rendered; if the parent frame is set to Hug rather than a fixed size, that size change propagates straight up through the auto-layout chain and can blow out spacing several levels away from where the swap actually happened. Preferred values exist for exactly this reason — they let the property's author curate a short list of components that are known to fit, instead of exposing the entire library and trusting every future consumer to pick something size-compatible.

A related, easy-to-miss detail: instance-swap and text properties both require the target layer to sit directly inside the component they're defined on. A text layer nested two instances deep isn't reachable from the outer component's own property panel — the property has to be created on whichever main component actually owns that text layer, one level in. This is precisely the seam that section 05 covers in more depth, because it's also where override inheritance most often breaks down.

04Variants are a contract, not a dropdown

Figma will technically let a component set contain variants with completely different internal layer trees — a "Hover" state built from a different set of layers than "Default," named differently, nested differently. Nothing stops a designer from building it that way. What breaks is the exact thing variants exist to provide: overrides and edits that survive a switch instead of silently resetting.

The rule Figma actually applies is narrow and unforgiving, and it's worth knowing precisely because it explains almost every "why did my change disappear" moment a design-systems team runs into. When an instance's variant is switched, an override survives only if both of these hold: the layer name where the change was made has to be identical between the old variant and the new one, and the property that changed has to have started out holding the same value on both variants before the change was made. Miss either condition and the instance quietly reverts to whatever the new variant defines by default — no dialog, no warning, just a change that no longer appears to have happened.

That's also why layer order and auto-layout position sit outside what any override — variant switch or otherwise — can touch at all. Reordering layers or moving something that auto layout is positioning requires either editing the main component directly or detaching the instance and giving up the contract entirely. Structural changes are reserved for the main component on purpose; if instances could make them too, propagation and override preservation would both become unpredictable.

Naming pattern for variant properties and how flat components merge into a set
Convention Example Why it matters
Property=Value pairs Type=Primary, Size=Large Each axis becomes its own dropdown in the panel
Forward-slash grouping Button/Primary/Large Flat, ungrouped components merge into these same axes when combined into a set
Consistent layer names Label, Icon in every variant The precondition for the override-survival rule above

05Nested override inheritance

Every override an instance holds is tracked by its full layer path, not just a layer name in isolation — "this exact position, in this exact tree, inside this exact nested instance." That distinction barely matters for a flat component, but it's the whole story once components start nesting inside each other, which is how most real design systems build compound components like list items, cards, and form fields.

Figma supports exposing a nested instance's own properties up to the parent component, so someone using the outer component doesn't have to drill two levels in just to swap an inner icon. It's a genuinely useful feature, and it comes with a real cost: the inner component's contract effectively flattens into the outer one, which means the two components' own property names can collide. Two unrelated components each shipping a property called simply Icon is fine in isolation and ambiguous the moment one is nested inside the other and both get exposed — the fix is a small naming discipline (prefixing nested, exposed properties by their source component) rather than anything Figma enforces automatically.

The more consequential fragility shows up with overrides made on a nested instance itself — say, swapping the icon inside a button that's nested inside a card. Because that override is chasing a layer path, not just a name, anything that shifts the path underneath it can strand the override even when every layer involved is still correctly named. Swapping the outer component's own variant, or duplicating an instance into a new context that changes which nested component sits at that path, is enough to lose it — a pattern experienced design-systems teams run into often enough that the practical guidance has become: don't rely on a doubly-nested override for anything that needs to be durable. If a difference genuinely needs to survive variant switches reliably, model it as a property on the main component directly rather than as an override two or three instances deep.

06Where variant sets break under real content

A variant grid built and tested against placeholder content — "Button," a single short English word, a generic icon — can look completely finished and still fail the moment real content, real translations, or real edge cases reach it. The failures cluster into a handful of repeatable patterns.

Combinatorial growth. Every axis added to a variant set multiplies the total, not adds to it. A Button with three types, three sizes, four states, and an icon toggle left inside the grid produces the full cross product — and pulling just that last axis out as a Boolean instead cuts the whole set in half:

How variant count grows when an axis stays in the grid vs. becomes a Boolean property
Axes in the variant grid Values Total variants
Type × Size × State × Icon 3 × 3 × 4 × 2 72
Type × Size × State (Icon pulled out as Boolean) 3 × 3 × 4 36

Every axis that's genuinely a yes/no toggle and doesn't change the component's underlying structure is a candidate for pulling out of the grid this way — the variant count only grows for axes that truly need to be there.

  • Text that doesn't fit the placeholder. A variant sized against a five-character label wraps, truncates, or blows out its container the moment it meets a translated string or a real person's name twice as long. A fixed-width variant and an auto-layout Hug variant of the same component can behave in completely different, untested ways once that happens — if only one was checked against long content, the other ships broken.
  • States that can't legally coexist. If State is a single variant axis, "Disabled" and "Hover" can never both be true at once, because an axis only ever holds one value. Real interfaces need a disabled button to suppress hover styling entirely, which usually means Disabled has to live as an independent property layered on top of the State axis, not as just another value inside it.
  • Directional assumptions baked into names. An icon-position axis named "Icon Left" / "Icon Right" encodes an LTR-only assumption and won't flip automatically for a right-to-left locale. Naming the same axis "Icon Leading" / "Icon Trailing" keeps the property meaningful regardless of reading direction — the rename costs nothing and the alternative costs a localization bug.
  • States nobody modeled at all. Default, Hover, Active, and Disabled cover the states a component looks like it needs in a design file. Loading, Error, and Empty are the states that only show up once a real API response is slow, fails, or returns nothing — and a "complete" variant set with no cell for any of them isn't actually complete, it's just untested against failure.

07A worked example

A Button component makes the property-type choices concrete. Every line below reflects a deliberate call about which mechanism fits which kind of change — not a default reached for out of habit:

Button (component set)
├─ Variant  Type   = Primary | Secondary | Ghost
├─ Variant  Size    = Small | Medium | Large
├─ Variant  State    = Default | Hover | Active
├─ Boolean  Disabled          (independent of State — suppresses Hover/Active)
├─ Boolean  Icon              (shown/hidden, not a variant axis)
├─ Instance-swap  Icon*       (nested slot, preferred values: 16px icon set only)
└─ Text     Label

Type, Size, and State stay on the variant grid because each one genuinely changes structure or styling in ways that need their own layer treatment. Disabled sits outside the grid entirely so it can be combined with any state. Icon is a Boolean paired with an instance-swap property rather than a fourth variant axis, which is what keeps the total variant count at 3 × 3 × 3 = 27 instead of 54.

One structural contract, three variants — an icon-fill override made on any one survives switching between them.
Same button, copy-pasted three times — one fix means three manual edits, and the radii and icon sizes have already begun to drift.

08Cheat sheet

  • Boolean toggles layer visibility only — nothing else. If a toggle needs to change a color or a size, it isn't a Boolean.
  • Instance-swap carries the swapped-in component's full footprint with it; use preferred values to keep swaps size-compatible inside Hug-sized parents.
  • An override survives a variant switch only if the layer name matches and the property held the same starting value on both variants — keep layer names identical across every variant in a set.
  • Layer order and auto-layout position can never be overridden on an instance; those changes belong on the main component.
  • Nested overrides are tracked by path, not name — treat anything overridden two or three instances deep as fragile, and push durable differences up into the main component instead.
  • Every variant axis multiplies the grid; pull genuinely binary axes out as Boolean properties before the combinatorics get out of hand.
  • Test variants against real content — long translated strings, Disabled+Hover together, RTL icon direction, Loading/Error/Empty — not just the placeholder text used to build them.