// figma_workflows · auto_layout_spec

Auto Layout Is a Constraint System, Not a Convenience Toggle

Most people learn Auto Layout by nudging settings until a frame looks right, then get stuck the first time it stretches, clips, or refuses to shrink. This is a spec-level read of what it's actually solving underneath the panel — resolvable rules, not guesses — so you can predict the outcome before you touch anything.

INTERMEDIATE · 15 MIN READ · FIGMA WORKFLOWS

§ 01

The Mental Model

A frame with Auto Layout applied does not have a size the way an ordinary rectangle does. Draw a plain rectangle and its width and height are data you typed in — fixed numbers, full stop. Apply Auto Layout to a frame and its size becomes the output of a small system of rules: how much space is available, what its children are asking for, and what its own sizing mode says to do with the difference. Nothing renders until that system is solved.

This is the same box model CSS Flexbox already implements, arriving at the same place from the same direction — Figma didn't invent a new spacing paradigm, it gave designers a panel over the one browsers already run. Every "why is my button suddenly full-width" moment is a resolution-order problem, not a bug: something upstream is asking for a size that has nowhere to come from, so Figma has to pick an answer for you.

Treat every Auto Layout frame as three declarations layered on top of each other, always in this order: how big is this frame allowed to be (its own sizing mode, relative to its parent), how is the space inside it divided (padding and gap), and how are its children positioned within what's left (alignment). Get the order right and the rest of this page is mostly vocabulary.

The three sizing modes Three blueprint panels showing Fixed, Hug, and Fill sizing behavior for an Auto Layout frame. FIXED 160 ignores parent and children HUG content child + padding shrinks to fit its children FILL remaining parent space grows to match parent
FIG. 01 — Fixed ignores everything, Hug points down toward children, Fill points up toward the parent.

§ 02

Three Sizing Modes, One Rule Each

Every Auto Layout frame, and every direct child of one, picks a resizing mode independently for each axis — width and height are set separately, so a button can be Fill horizontally and Hug vertically at the same time.

Fixed — an explicit number. The frame ignores its children and its parent entirely on that axis. Use it for things that must never move: an icon slot, a fixed-width sidebar, a toggle switch.

Hug contents — the frame sizes itself to wrap its children plus its own padding, and nothing else. Its size on that axis is an output of what's inside it, not an input you set directly, which is why you resize a Hug frame by resizing its children or its padding — never the frame.

Fill container — the frame asks its parent for whatever space is left over after siblings and padding are accounted for. Its size on that axis is an output of its parent, not of its own content, so a Fill frame's content can overflow if the parent turns out smaller than expected — filling comes with no minimum guarantee unless you add one.

The one rule that explains almost every "unexpected" resize: Fill points up toward the parent, Hug points down toward the children. Set a frame to Hug on an axis, then give one of its direct children Fill on that same axis, and you've built two requests staring at each other with no third value to resolve against. Figma breaks the tie by quietly converting one side to Fixed — which looks like a bug the first time you see it, and is actually the only sane way to terminate an unsolvable loop. The fix isn't a setting, it's structural: Fill only means something once some ancestor on that axis has already landed on a real number, whether that's a Fixed frame, the artboard edge, or a browser viewport.

§ 03

Padding and Gap Are Different Properties

Two spacing values live on every Auto Layout frame, and it's worth being precise about which one you're reaching for, because they resize completely different things.

Padding is the space between the frame's own edge and its children — inside a Hug frame, padding is part of what the frame is hugging, so changing padding changes the frame's outer size. Padding can be set as one uniform number, a horizontal/vertical pair, or independently on all four sides.

Gap (sometimes labeled "item spacing") is the space between siblings, not between the frame and its edge. Gap can be a fixed number — every gap in the frame is exactly that many pixels — or set to distribute automatically, which pushes the first and last child flush to the padding edge and lets the gap absorb whatever room is left between them, growing as the frame grows.

The distinction matters most when a Hug frame "won't shrink": if you're dragging the gap value and the frame's outer bound isn't moving, that's correct behavior — gap only ever redistributes space between children, and padding is the only spacing value that changes a Hug frame's outer size.

§ 04

Alignment: Two Axes, Nine Positions

Every Auto Layout frame has a primary axis (the direction of flow — the axis Gap operates on) and a counter axis (perpendicular to it). Alignment is set independently for each, which is why the alignment control in the panel is a 3×3 grid rather than a single dropdown: three positions along the primary axis, times three along the counter axis, gives nine total combinations.

Primary-axis alignment controls how children pack along the direction of flow — start, center, end, or, via the Auto gap setting from the previous section, spread evenly with the leftover space pushed between items rather than gathered on one side.

Counter-axis alignment controls where children sit on the perpendicular axis — for a horizontal frame, that's vertical position (top, center, bottom); for a vertical frame, it's horizontal position (left, center, right).

This maps directly onto Flexbox's justify-content (primary axis) and align-items (counter axis) — see the handoff table in section eight for the full property list.

§ 05

Nesting: How Constraints Cascade

The real power of Auto Layout only shows up once frames are nested, and the reason it holds together is that each frame solves its own local system independently, then hands its resolved size up to its parent as if it were a single, ordinary child — the parent doesn't know or care that its child is itself a small layout engine with three more children inside it.

Anatomy of a nested comment card Blueprint schematic of a card component: an outer vertical Hug frame containing a horizontal byline row and a vertical body-text frame, with padding and gap annotated. CARD · VERTICAL · HUG padding 20 BYLINE · HORIZONTAL · HUG FIX name — FILL time — FIX gap 8 BODY · VERTICAL · HUG ↳ new line typed here grows this frame, which grows the card above card height = output, not an input
FIG. 02 — Each frame resolves its own local rules first; the result rolls up to its parent as one ordinary child.

Take the comment card above: an outer vertical frame set to Hug (the card itself), containing an inner horizontal frame for the byline (an avatar at Fixed size, a name set to Fill, and a timestamp at Fixed size), followed by a vertical frame for the body text, also set to Hug. Type a longer comment, and the body-text frame grows taller because its own Hug rule resolves against its now-taller text child. That new height rolls up to the outer card frame — also Hug — which grows to match. Nothing in this chain was told "make the card taller when the comment is long"; every frame just resolved its own three-declaration stack from section one, and the result composes correctly by construction.

The debugging consequence is the one worth remembering: a change three frames deep can only propagate upward through frames that are set to Hug on the relevant axis. The moment it hits a Fixed or Fill frame — a frame whose size on that axis doesn't come from its children — the change stops there and gets absorbed, either by clipping if there's no room, or by whitespace if there's too much. If resizing something isn't affecting a distant ancestor the way you'd expect, the frame sitting between them is the first place to check.

§ 06

Wrap and the Min/Max Escape Hatches

Horizontal and vertical flows (not the Grid flow) support a Wrap setting: once children run out of room on the primary axis, instead of overflowing or shrinking past their limits, the frame starts a new row — or column — and continues laying out there. Wrapped frames get a second, independent gap value for the space between rows, separate from the gap between items within a row.

Wrap on its own doesn't create a responsive layout — it just gives overflow somewhere to go. What decides when something wraps is the min-width (or min-height) constraint layered on top of a child's Fill or Hug sizing: a Fill child with a 200px minimum keeps growing to fill its row right up until the row can no longer offer it 200px, at which point it drops to the next line instead of shrinking further. Max-width and max-height work the same way in reverse, capping how far a Fill child is allowed to stretch.

Worth flagging plainly: min/max constraints across several Fill children inside a wrapped parent are one of the rougher edges of the engine as of this writing — resize order can produce values that look inconsistent across siblings that should be symmetric. If a layout depends on precise min/max behavior across many wrapped Fill items, test the actual file rather than assuming the numbers will resolve the way the panel implies.

§ 07

Absolute Position, On Purpose

Occasionally an element needs to sit on top of an Auto Layout frame without taking part in its flow at all — a notification badge overlapping the corner of an icon, a close button pinned to a modal header regardless of how long the title text runs. Toggling a layer to Absolute Position removes it from the gap and alignment system entirely: it no longer consumes a slot, doesn't push siblings, and is positioned instead by direct offsets against its parent frame — the same way position: absolute behaves against a position: relative parent in CSS. Use it deliberately and sparingly: it's an exception to the constraint system, not a substitute for it, and a layout built mostly out of absolutely positioned children has quietly opted out of everything Auto Layout is for.

§ 08

Dev Handoff: Mapping to CSS Flexbox

None of the vocabulary above is Figma-specific — it's a panel over the same model Flexbox already implements, which is exactly why "translate this frame to code" is usually a mechanical exercise rather than a design decision, provided both sides are using the same terms.

/ figma_to_flexbox_property_map
Figma property CSS Flexbox equivalent
Direction: Horizontal / Verticalflex-direction: row / column
Wrapflex-wrap: wrap
Gap (fixed number)gap
Gap: Auto / space betweenjustify-content: space-between
Paddingpadding (shorthand or per side)
Resizing: Fixedfixed width/height, flex-grow: 0, flex-shrink: 0
Resizing: Hug contentswidth: fit-content (no flex-grow)
Resizing: Fill containerflex: 1 1 0%
Primary-axis alignmentjustify-content
Counter-axis alignmentalign-items (or align-self per child)
Min / Max width or heightmin-width / max-width, min-height / max-height
Absolute Positionposition: absolute on a position: relative parent

§ 09

Five Pitfalls That Look Like Bugs

  • 01

    Fill inside a Hug parent, same axis. The circular dependency from section two — Figma silently converts one side to Fixed. Fix the structure, not the symptom: give an ancestor on that axis a resolved size, or move the Fill child under a different parent.

  • 02

    Wrapping a shape-plus-text group instead of applying Auto Layout directly. Group a background rectangle with a text layer first, then apply Auto Layout to the group, and the shape and text end up negotiating Fill/Hug with each other instead of the text simply negotiating with the frame. Select the text layer alone and apply Auto Layout to it directly; give the resulting frame a fill color and corner radius, and it becomes the button background with one fewer layer to reason about.

  • 03

    Adjusting gap when the frame won't shrink. If a Hug frame's outer size isn't moving, check padding, not gap — gap only ever redistributes space between existing children.

  • 04

    Forgetting counter-axis stretch. Children default to Hug on the counter axis unless told otherwise, so a short child sitting next to a tall one in the same row won't automatically match its height. For a uniform row or column, set the shorter children's counter-axis sizing to Fill rather than adjusting alignment.

  • 05

    Trusting min/max precision across many wrapped Fill children. As noted in section six — verify in the actual file for anything layout-critical, rather than assuming the panel's numbers will resolve symmetrically.

§ 10

Quick Reference

/ rules_of_thumb
Property What it does Rule of thumb
FixedIgnores children and parentUse for things that must never move
Hug contentsSizes to children + paddingResize by resizing children or padding, never the frame
Fill containerTakes leftover space from parentOnly meaningful once an ancestor has a resolved size
PaddingSpace between frame edge and childrenChanges a Hug frame's outer size
GapSpace between siblingsNever changes a Hug frame's outer size
WrapNew row/column on overflowPairs with min-width to decide when to wrap
Absolute PositionRemoves a child from the flowDeliberate exception, not a default

Auto Layout rewards being read once, carefully, the way you'd read a spec instead of a tooltip. After that, most of what looks like unpredictable behavior turns out to be the constraint system doing exactly what it was asked to do.