This article records the sizing rules I currently use when building components with Figma Auto Layout, especially how I choose between Fixed, Hug, and Fill, and where those choices can behave differently once the design is implemented in code.
I usually start by asking where a dimension comes from. Some values can be derived directly from content and padding. Others are defined by an external component rule or by the parent layout.
For controls such as buttons, single-line inputs, selects, and tags, height can usually be calculated as:
height = top padding + content height + bottom paddingIf the stroke participates in layout, include it as well.
For these components, I usually use Hug. When line-height, padding, or tokens change, the component size updates with them.
A Fixed height introduces another value that must stay in sync. A component can still appear to be 40px tall even after its internal padding has drifted away from the original specification. I have run into this in a real component set: four out of five size variants had padding values that no longer matched the measured Figma values, while their fixed heights still looked correct.
Keeping derived values in the layout system reduces this kind of hidden drift.
| Figma | Common CSS equivalent | Where the size comes from |
|---|---|---|
| Fixed | width: 40px / height: 40px | An explicit external dimension |
| Hug contents | auto / fit-content | Content and padding |
| Fill container | flex: 1 / width: 100% | Space allocated by the parent |
Each axis can use a different sizing mode. A typical single-line input often uses:
Width = Fill
Height = HugThe width follows the form container, while the height comes from the text and vertical padding.
Similar concepts appear in other UI systems:
| Platform | Content-driven sizing | Fixed sizing |
|---|---|---|
| CSS Flexbox | auto / fit-content | Explicit px |
| Flutter | MainAxisSize.min | SizedBox(height: 40) |
| SwiftUI | Intrinsic content size | .frame(height: 40) |
| Traditional Android layouts | wrap_content | Explicit dp |
The APIs differ, but the sizing source is usually the same question.
Content-driven: use Hug. Common examples include Button, single-line Input, Select, Tag / Chip, Badge, Tooltip, Accordion content, and content cards. Their height is closely tied to text, padding, and content length. Hug keeps those relationships synchronized when the component changes. Multi-line containers benefit from this even more because text length can vary significantly.
Rule-driven: use Fixed. Some dimensions are part of the component specification itself: square icon buttons; avatars; controls that must stay at 32 / 40 / 48px; rows aligned to a fixed vertical rhythm; toolbar or canvas controls with a defined hit area. For a 40 x 40 icon button, changing the icon from 16px to 20px should not change the outer button size. Fixed expresses that rule clearly.
Parent-driven: use Fill. Fill works well when the component should consume available space, for example: form inputs spanning the row; main content areas in split layouts; search fields that take the remaining toolbar space; responsive cards and list items.
I treat width and height independently. Many everyday components use Width = Fill / Height = Hug, while compact icon controls may use Fixed on both axes.
When a dimension is unclear, I check whether the current system can already derive it from content, padding, tokens, or parent constraints.
If the value can be calculated consistently, I usually avoid storing an additional Fixed value.
A standalone Fixed dimension makes more sense when it carries a real rule such as touch target size, grid rhythm, business constraints, performance requirements, or external alignment.
A common case is a Button and Select placed side by side with slightly different heights. Setting both to 40px will visually align them, but I first check:
If both components are intended to follow the same sizing rule, aligning these inputs usually makes their Hug heights match naturally.
Fixed is useful when the component specification explicitly requires a fixed size. It is less useful as a permanent patch for inconsistent internal tokens.
This is a common source of 1-2px differences between design and implementation.
Figma Stroke can be aligned Inside, Center, or Outside. With an Inside stroke, a 1px line is drawn within the Frame boundary and does not expand the frame size.
CSS border has no equivalent alignment control. Its final size depends on box-sizing and whether an explicit dimension exists.
With an explicit height:
height: 40px;
border: 1px solid;
box-sizing: border-box;The outer box stays 40px. The border is included within that dimension, which behaves similarly to an Inside stroke.
With auto height, without an explicit height, the browser derives the final box from its contents:
final height = content + padding + borderIn this case, a Figma component that measures 40px with Hug + Inside Stroke can become 42px in CSS if the implementation simply uses content, padding, and a 1px border on both sides.
When pixel-level Inside Stroke matching matters, an inset box shadow can draw the line without taking layout space:
box-shadow: inset 0 0 0 1px var(--border-color);This can be useful when the implementation needs to match Figma's Inside stroke dimensions closely. For ordinary product UI, keeping padding, line-height, and border rules consistent is often sufficient.
Virtualized lists are a common reason to use Fixed sizing for performance.
Large tables, chat histories, and log viewers often render only the rows around the viewport. The virtualization layer needs to estimate item positions and total scroll height.
Some implementations therefore require a fixed item height, or at least a small set of predictable heights. A row may contain dynamic text while still being constrained by the rendering architecture.
This is worth confirming early. A design that assumes unlimited Hug behavior can diverge significantly from an implementation that needs fixed row heights for virtualization.
Older design tools were centered on a freeform canvas, where frame dimensions were manually drawn as fixed numbers.
Auto Layout brought content flow, padding, gaps, and parent-child sizing relationships into the design tool, making Figma layouts much closer to real interface layout systems.
Web UI has long relied on content-driven sizing. A basic button without explicit width or height naturally grows with its text and padding.
My current mapping is therefore simple: content determines the size → Hug; parent layout determines the size → Fill; component or product rules define the size → Fixed.
Keeping that relationship consistent between Figma and code makes token changes and responsive behavior much easier to maintain.