repeated-component-alignment
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRepeated Component Alignment
重复组件对齐
When a component is rendered many times — a card grid, a list, a table, a nav menu, a row of KPI tiles, a feed — it stops being a single box and becomes a pattern. The value of a pattern is rhythm: the eye learns the layout once and scans the same slot across every instance (the title row, the price row, the action row). Variable content length breaks that rhythm unless the component is built to absorb it.
The principle is general. A card is the most common case, but the same rule governs list rows, table cells, nav items, tiles, comment entries, dashboard widgets, search results — anything repeated. Treat each as a fixed slot model, not a free-form container.
The goal: content of any length, instances that look identical. This is partly content production (write to a target length) and partly layout engineering (build slots that tolerate the variance). This skill covers the layout half and where the two meet.
当一个组件需要多次渲染时——比如卡片网格、列表、表格、导航菜单、KPI磁贴行、信息流——它不再是单一容器,而是一种模式。模式的价值在于韵律:用户只需学习一次布局,就能在每个实例中扫视相同的插槽(标题行、价格行、操作行)。除非组件被设计为可容纳内容长度变化,否则可变的内容长度会打破这种韵律。
这一原则具有通用性。卡片是最常见的场景,但同样的规则适用于列表行、表格单元格、导航项、磁贴、评论条目、仪表盘组件、搜索结果——所有需要重复渲染的元素。应将它们视为固定插槽模型,而非自由容器。
目标:支持任意长度的内容,且所有实例外观一致。这部分依赖内容创作(按目标长度撰写),部分依赖布局工程(构建可容纳差异的插槽)。本文将介绍布局部分,以及两者的结合点。
The Slot Model
插槽模型
Name the slots once and treat them as a contract every instance honours. A product card, as a worked example:
┌──────────────────────┐
│ [media] │ ← fixed aspect ratio
│ │
├──────────────────────┤
│ ● Badge │ ← optional slot, space reserved
│ Title │ ← clamp to N lines
│ Subtitle / meta │
│ │
│ Description text… │ ← flexible slot, grows
│ │
├──────────────────────┤
│ €19.99 Read more│ ← anchor, pinned to bottom
└──────────────────────┘The same three rules apply to a list row (avatar · name · meta · status pinned right), a KPI tile (label · big number · trend pinned bottom), or a search result (title · url · snippet clamped):
- Every slot has a fixed position, whether or not it has content in a given instance.
- One slot absorbs the variance (usually the description/snippet). All others are fixed or clamped.
- Anchor elements are pinned — the primary action or value (CTA, price, status, trend) sits at the same position in every instance regardless of how much content is above or beside it.
先定义插槽,将其视为每个实例都需遵循的约定。以商品卡片为例:
┌──────────────────────┐
│ [media] │ ← 固定宽高比
│ │
├──────────────────────┤
│ ● Badge │ ← 可选插槽,预留空间
│ Title │ ← 限制为N行
│ Subtitle / meta │
│ │
│ Description text… │ ← 弹性插槽,可扩展
│ │
├──────────────────────┤
│ €19.99 Read more│ ← 锚点,固定在底部
└──────────────────────┘同样的三条规则适用于列表行(头像 · 名称 · 元数据 · 状态固定在右侧)、KPI磁贴(标签 · 大数值 · 趋势固定在底部)或搜索结果(标题 · 链接 · 摘要截断):
- 每个插槽位置固定,无论某个实例中该插槽是否有内容。
- 仅一个插槽容纳长度变化(通常是描述/摘要)。其他插槽均为固定或截断模式。
- 锚点元素固定——主要操作或数值(CTA、价格、状态、趋势)在每个实例中的位置保持一致,不受其上方或旁边内容长度的影响。
Aligning the Anchor
锚点对齐
The single most common defect: text of different lengths makes the anchor element (a "Read more" link, a price, a status chip) float to a different position in each instance. Fix it by letting the flexible slot grow and pushing the anchor to a fixed edge.
Vertical layout (cards, tiles) — pin the footer to the bottom:
css
.item {
display: flex;
flex-direction: column;
height: 100%; /* fill the grid/flex track */
}
.item__media {
aspect-ratio: 4 / 3; /* never let media height vary */
object-fit: cover;
}
.item__description {
flex: 1; /* the flexible slot absorbs the slack */
}
.item__footer {
margin-top: auto; /* pin the anchor (price + CTA) to the bottom */
}For instances to be equal height as siblings, the container track must stretch them — CSS Grid and Flex do this by default (). Then makes each instance fill its track, and aligns every footer.
align-items: stretchheight: 100%margin-top: autocss
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: var(--space-4);
/* align-items: stretch is the default — do not override it */
}Horizontal layout (list rows, table cells) — pin the anchor to the right:
css
.row {
display: flex;
align-items: center;
gap: var(--space-3);
}
.row__title {
flex: 1; /* title absorbs the width variance */
min-width: 0; /* REQUIRED for the title to be allowed to shrink/ellipsis */
}
.row__status {
margin-left: auto; /* status pinned to the right edge */
}Do not force equal size with a hard-coded — the tallest natural instance sets the size, and content beyond it clips or overflows. Let the track stretch and pin the anchor.
height最常见的问题:不同长度的文本会导致锚点元素(如“查看更多”链接、价格、状态标签)在每个实例中浮动到不同位置。解决方法是让弹性插槽扩展,将锚点推至固定边缘。
垂直布局(卡片、磁贴)——将页脚固定在底部:
css
.item {
display: flex;
flex-direction: column;
height: 100%; /* 填充网格/flex轨道 */
}
.item__media {
aspect-ratio: 4 / 3; /* 媒体高度保持不变 */
object-fit: cover;
}
.item__description {
flex: 1; /* 弹性插槽容纳剩余空间 */
}
.item__footer {
margin-top: auto; /* 将锚点(价格+CTA)固定在底部 */
}要让同级实例高度相等,容器轨道必须拉伸它们——CSS Grid和Flex默认会这样做()。然后让每个实例填充其轨道,使所有页脚对齐。
align-items: stretchheight: 100%margin-top: autocss
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: var(--space-4);
/* align-items: stretch 是默认值——请勿覆盖 */
}水平布局(列表行、表格单元格)——将锚点固定在右侧:
css
.row {
display: flex;
align-items: center;
gap: var(--space-3);
}
.row__title {
flex: 1; /* 标题容纳宽度变化 */
min-width: 0; /* 必须设置,允许标题收缩/显示省略号 */
}
.row__status {
margin-left: auto; /* 状态固定在右侧边缘 */
}请勿使用硬编码的强制设置相等尺寸——最高的自然实例会决定尺寸,超出部分会被裁剪或溢出。应让轨道拉伸并固定锚点。
heightReserve Space for Optional Slots
为可选插槽预留空间
A slot that appears in some instances and not others — a badge, a discount label, a "verified" tick — shifts everything after it on the instances that have it, breaking alignment. Two fixes:
- Reserve the slot — always render the container at a fixed size, empty when there is no content:
css
.item__badge-row {
min-height: 24px; /* always occupies the row */
}- Overlay the slot — position it absolutely so it never participates in the flow:
css
.item__badge {
position: absolute;
top: var(--space-2);
left: var(--space-2);
}Reserve when the element is inline metadata; overlay when it is a marker on media (Sale, New). Either way, the slot after it must start at the same position in every instance.
有些插槽仅在部分实例中出现——比如徽章、折扣标签、“已验证”标记——这会导致有该插槽的实例中后续元素位置偏移,破坏对齐。有两种解决方法:
- 预留插槽空间——始终渲染固定尺寸的容器,无内容时保持为空:
css
.item__badge-row {
min-height: 24px; /* 始终占据该行空间 */
}- 叠加插槽——绝对定位元素,使其不影响文档流:
css
.item__badge {
position: absolute;
top: var(--space-2);
left: var(--space-2);
}如果元素是行内元数据,选择预留空间;如果是媒体上的标记(如“促销”“新品”),选择叠加。无论哪种方式,后续插槽在所有实例中的起始位置必须一致。
Clamp Overflowing Text — and Give the Full Value Back
截断溢出文本——并提供完整内容
When a slot must be fixed-size but its content varies, clamp it to a line count and signal the cut with an ellipsis. This keeps the geometry stable. But truncation hides information — always make the full value recoverable.
Multi-line text — clamp to N lines:
css
.clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}Single-line values (names, SKUs, paths) — ellipsis:
css
.truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0; /* needed inside a flex row to allow shrinking */
}Recover the full value. A clamped or ellipsised string is a usability trap if the full text is unreachable. Provide it:
- Native tooltip for plain text: . Zero cost, works everywhere, but hover-only (not touch) and unstyled.
<span title="Full value here">…</span> - Custom tooltip when you need touch support, styling, or rich content.
- Reveal in place when the full content is the point — a "Read more" toggle that expands the instance or opens a detail view, rather than permanently hiding text the user needs.
Only attach a tooltip when text is actually truncated — aon a string that fits adds a redundant hover. Detect overflow (title) and setscrollWidth > clientWidthconditionally.title
The hierarchy of handling variable length:
- Write to length — the cleanest fix. Give content authors a target (e.g. titles ≤ 60 chars, snippets ≤ 120) so most values never need truncating. Layout tricks are a safety net, not the primary plan.
- Clamp + recover — when authored length cannot be guaranteed (user-generated, third-party feeds, i18n expansion).
- Let one slot grow — for the single slot allowed to vary, absorb the variance with flex rather than truncating, and pin everything after it.
当插槽必须固定尺寸但内容长度可变时,将其限制为指定行数,并使用省略号标记截断。这样能保持布局稳定。但截断会隐藏信息——必须始终让用户可获取完整内容。
多行文本——限制为N行:
css
.clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}单行内容(名称、SKU、路径)——显示省略号:
css
.truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0; /* 在flex行内需要设置,允许文本收缩 */
}恢复完整内容。如果用户无法获取完整文本,截断或显示省略号的字符串会成为可用性陷阱。可通过以下方式提供完整内容:
- 原生提示框:适用于纯文本,使用。零成本,全平台可用,但仅支持 hover(不支持触摸)且样式不可定制。
<span title="完整内容">…</span> - 自定义提示框:当需要支持触摸、自定义样式或富内容时使用。
- 原位展开:当完整内容是核心信息时——使用“查看更多”切换按钮展开实例或打开详情页,而非永久隐藏用户需要的文本。
仅当文本确实被截断时才添加提示框——如果字符串完全显示,添加会导致多余的hover提示。检测溢出(title)并按需设置scrollWidth > clientWidth。title
处理内容长度变化的优先级:
- 按目标长度撰写——最理想的解决方案。为内容创作者设定目标(如标题≤60字符,摘要≤120字符),这样大多数内容无需截断。布局技巧只是安全网,而非主要方案。
- 截断+恢复——当无法保证内容长度时(用户生成内容、第三方信息流、国际化扩展)。
- 允许单个插槽扩展——对于允许变化的单个插槽,使用flex容纳长度变化,而非截断,并固定后续所有元素。
Internationalisation Note
国际化注意事项
Text expands when translated — German and Finnish commonly run 30–40% longer than English. A component that aligns perfectly in English can break in another locale. Design slots for the long case: clamp text, reserve optional slots, give flex rows , and never assume a label fits on one line because it does in the source language.
min-width: 0文本翻译后会变长——德语和芬兰语通常比英语长30%-40%。在英语中对齐完美的组件,在其他语言环境中可能会失效。需为长文本场景设计插槽:截断文本、预留可选插槽空间、为flex行设置,切勿因为源语言中标签能单行显示就假设所有语言都能如此。
min-width: 0Review Checklist
审查清单
- Is the repeated component a defined slot model — every instance fills the same slots in the same order?
- Are sibling instances equal size via a stretched grid/flex track, not a hard-coded height?
- Is the anchor (CTA / price / status / value) pinned — for columns,
margin-top: autofor rows — so it aligns across instances?margin-left: auto - Does exactly one slot absorb length variance, with the rest fixed or clamped?
- Do optional slots (badge, label) reserve space or overlay, so they never shift the slots after them?
- Does media use a fixed so its size never varies?
aspect-ratio - Are multi-line slots clamped to a line count and single-line values ellipsised?
- Do flex rows that truncate have so the text is allowed to shrink?
min-width: 0 - Is the full value recoverable (title/tooltip/reveal) wherever text is truncated?
- Is the tooltip applied only when the text actually overflows?
- Do content authors have target lengths, so truncation is a safety net rather than the norm?
- Have slots been checked against the longest-translating locale, not just the source language?
- 重复组件是否采用了定义好的插槽模型——每个实例按相同顺序填充相同插槽?
- 同级实例是否通过拉伸的网格/flex轨道保持尺寸相等,而非硬编码height?
- 锚点(CTA/价格/状态/数值)是否固定——垂直布局使用,水平布局使用
margin-top: auto——确保在所有实例中对齐?margin-left: auto - 是否仅一个插槽容纳长度变化,其余插槽为固定或截断模式?
- 可选插槽(徽章、标签)是否预留空间或叠加,避免偏移后续插槽?
- 媒体是否使用固定,确保尺寸不变?
aspect-ratio - 多行插槽是否限制行数,单行内容是否显示省略号?
- 需要截断文本的flex行是否设置了,允许文本收缩?
min-width: 0 - 所有截断文本的位置是否都能获取完整内容(标题/提示框/展开)?
- 提示框是否仅在文本确实溢出时才添加?
- 是否为内容创作者提供了目标长度,使截断仅作为安全网而非常规操作?
- 是否针对翻译后最长的语言环境检查插槽,而非仅检查源语言?