← ALL NOTES
METHOD·2026.09·6 MIN READ
Figma 组件尺寸:Fixed、Hug 和 Fill 怎么选
handoffdesign-systems

这篇记录我在设计系统里处理组件尺寸时的一套判断方法,重点放在 Figma Auto Layout 的 Fixed / Hug / Fill 怎么选,以及这些设置落到代码里时容易出现的差异。

我现在会先看一个尺寸值是怎么产生的:它是可以从内容和内边距里直接推出来,还是来自组件之外的约束。这个判断基本能覆盖大部分高度和宽度设置。

可推导值尽量交给布局计算

Button、单行 Input、Select、Tag 这类组件,高度通常可以直接由下面几项得到:

高度 = 上内边距 + 内容高度 + 下内边距

如果描边会占用布局空间,再把描边计入。

这种情况下我更倾向用 Hug。文字行高、padding 或 token 调整以后,组件尺寸会自动跟着更新,不需要再维护一份独立高度。

Fixed 会多出一个需要同步的数值。组件看起来仍然是 40px 高,并不代表内部的行高和 padding 还保持原来的关系。之前我们就遇到过这种情况:5 个尺寸里有 4 个的 padding 已经和 Figma 实测值不一致,但固定高度一直没变,所以问题很难从外观看出来。

这和 token 的维护逻辑很接近。能从已有规则稳定推导出来的值,尽量让系统自己算,减少重复数据。

Fixed、Hug、Fill 分别解决什么

Figma Auto Layout 在每个轴上都可以单独设置尺寸方式。

FigmaCSS 常见对应尺寸来源
Fixedwidth: 40px / height: 40px外部给定一个明确尺寸
Hug contentsauto / fit-content内容和内边距共同决定
Fill containerflex: 1 / width: 100%父容器分配可用空间

两个轴可以使用不同模式。常见的单行输入框就是:

宽度 = Fill
高度 = Hug

宽度跟随容器变化,高度由单行文字和上下 padding 算出来。

在其他 UI 系统里也有类似概念:

平台 / 框架内容驱动固定尺寸
CSS Flexboxauto / fit-content显式 px
FlutterMainAxisSize.minSizedBox(height: 40)
SwiftUIintrinsic content size.frame(height: 40)
Android 传统布局wrap_content固定 dp

具体 API 会不同,但判断尺寸来源的方法基本一致。

我怎么判断组件高度

内容决定高度:优先 Hug。 常见组件包括 Button、单行 Input、Select、Tag / Chip、Badge、Tooltip、Accordion 内容区、普通内容卡片。这些组件的高度和内容、padding 有直接关系。文本变多、字号变化或组件 token 调整时,Hug 可以保持结构同步。多行内容容器尤其适合内容驱动。如果直接写死高度,内容稍微变化就可能出现截断或额外滚动。

外部规则决定高度:使用 Fixed。 有些尺寸本身就是设计规则的一部分:纯图标方形按钮;Avatar;明确要求 32 / 40 / 48px 的控件外框;需要对齐特定列表节奏或网格的行;某些工具栏和画布控件。这时内部内容大小不能反过来改变外框。比如一个 40 × 40 的 Icon Button,换成 16px 或 20px 图标以后,按钮本身仍然应该保持 40 × 40。

Fill:尺寸由父级决定。 Fill 更适合需要使用剩余空间的场景:表单输入框撑满一行;左右布局中的主内容区域;工具栏中需要占据剩余空间的搜索框;响应式卡片或列表项。

我一般会分别判断两个轴,不会把一个组件简单归类成"Fixed 组件"或"Hug 组件"。很多常规 UI 都是 Width = Fill / Height = Hug,也有 Width = Fixed / Height = Fixed 的图标控件。

一个实用检查:这个数值能不能直接算出来

碰到不确定的尺寸,我会检查:这个值能不能从现在已有的内容、padding、token 或父级约束里稳定算出来?

能稳定推导,就尽量减少额外 Fixed 值。

需要单独存在的 Fixed 数值通常承担了额外规则,例如固定触控尺寸、网格节奏、业务要求、性能限制或外部对齐关系。

这个检查也适合设计系统里的其他 token。重复保存同一个结果,会增加以后同步出错的机会。

不要用 Fixed 掩盖组件 token 不一致

一个很常见的场景是:Button 和 Select 放在同一行,看起来高度不一致,于是直接把两者都设成 40px。

我会先检查:字号和 line-height 是否一致;上下 padding 是否来自同一套 spacing token;border 是否参与了最终尺寸;内部图标是否改变了布局。

如果它们本来就应该共享同一个高度规则,把这些基础参数统一后,Hug 通常会自然得到相同尺寸。

Fixed 适合表达明确的固定规则,不适合长期补偿两个组件内部结构不同步的问题。

Figma Stroke 和 CSS Border 的差异

这里是设计稿转代码时很容易出现 1–2px 偏差的地方。

Figma 的 Stroke 可以选择 Inside、Center、Outside。当 Stroke 设置为 Inside 时,1px 描边画在 Frame 边界内部,不会把 Frame 本身再撑大。

CSS 的 border 没有同样的描边对齐选项。最终尺寸还会受到 box-sizing 和是否显式指定尺寸的影响。

有显式高度时,假设:

CSS
height: 40px;
border: 1px solid;
box-sizing: border-box;

最终外框仍然是 40px。Border 被计算在这个高度里面,内容区域相应缩小。这种效果比较接近 Figma 的 Inside Stroke。

高度由内容自动撑开时,如果没有写 height,浏览器需要根据内容计算盒子尺寸:

最终高度 = content + padding + border

这时 box-sizing: border-box 没有一个提前指定好的总高度可以约束,border 会参与最终外框尺寸。所以 Figma 里一个 Hug + Inside Stroke 的 40px 组件,直接翻成 CSS 的 content + padding + border 后,有可能变成 42px。

需要像素级贴近 Inside Stroke 时,可以考虑用 inset box-shadow 画内描边:

CSS
box-shadow: inset 0 0 0 1px var(--border-color);

box-shadow 不占布局空间,可以更接近 Figma Inside Stroke 的视觉表现。是否要这样做取决于项目要求。普通业务 UI 通常直接统一 padding、line-height 和 border 计算方式就够了;需要严格对齐 Figma 尺寸时,这个技巧比较有用。

一个工程例外:虚拟化长列表

虚拟列表是 Fixed 很典型的工程使用场景。

几千行表格、聊天记录或日志列表为了性能,通常只渲染视口附近的内容。虚拟滚动需要提前估算每一项的位置和整个滚动区域的长度。

如果每一行高度都完全依赖动态内容,滚动位置计算会复杂很多。有些虚拟列表实现因此要求固定 item height,或者至少要求一个稳定的估算值。

这时组件内容看起来可以自然增长,工程层仍然可能指定固定高度或有限几档高度。这个约束需要在设计阶段就确认,否则设计稿里无限 Hug,开发实现里强制 Fixed,很容易在长文本和异常数据上出现差异。

Auto Layout 为什么让 Hug 变得重要

早期设计工具更接近自由画布:Frame 的宽高由设计师直接拖出来,每个尺寸天然都是一个具体数字。

Auto Layout 把内容流、padding、gap 和父子尺寸关系引入设计工具以后,Figma 的布局模型开始更接近真实 UI。

网页本身一直大量使用内容驱动尺寸。一个普通按钮在没有显式宽高时,本来就会根据文字和 padding 自然撑开。

所以我现在做组件时,会尽量把 Figma 里的尺寸来源和代码里的布局来源对应起来:内容负责撑开的尺寸,用 Hug;父级分配的尺寸,用 Fill;产品规则明确指定的尺寸,用 Fixed。

这样设计稿里的结构关系更容易直接落到代码里,后续改 token 或响应式布局时也少一层手动同步。

我现在会检查的几件事
  • 这个尺寸来自内容、父容器,还是明确的固定规则;
  • Button / Input / Select 的高度能否由 line-height + padding 推导;
  • 同类组件是否共享同一套 spacing 和 typography token;
  • Width 和 Height 是否需要分别选择不同模式;
  • Fixed 是否正在补偿内部 token 不一致;
  • Stroke / Border 是否造成了 1–2px 的尺寸差异;
  • 需要严格复刻 Inside Stroke 时,是否适合使用 inset box-shadow;
  • 虚拟列表、性能优化等工程条件是否要求固定 item height。
CONTINUE READING
从 Figma 组件属性到代码 Props2026.09Figma 设计系统的工程化落地2026.09