ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Astryx 布局原语家族契约:Stack、Grid、Center 与共享空间词汇表

2026/9/15 20:34:04 拓冰建站 浏览量
Astryx 布局原语家族契约:Stack、Grid、Center 与共享空间词汇表 Astryx 布局原语家族契约Stack、Grid、Center 与共享空间词汇表【免费下载链接】astryxAn open source design system thats fully customizable and agent ready项目地址: https://gitcode.com/GitHub_Trending/as/astryx导读布局原语layout primitives是任何设计系统里最高频、最容易被各业务团队各自为政的部分。Astryx 通过一份 family contract 文档docs/families/layout-primitives.md将 Stack、HStack、VStack、StackItem、Grid、GridSpan、Center 七个成员收敛为同一个小词汇表选择 Stack、Grid 或 Center 只改变排列模型而间距刻度、尺寸语义、对齐方向、padding 优先级这些共享概念在家族内保持唯一含义。本文以这份契约为骨架结合 packages/core/src 下的真实实现与测试讲解每个成员的能力边界、共享不变量FR1–FR9、允许的组件差异AV1–AV7以及一份可直接落地的实战组合方案帮助你写出风格一致、可维护、可主题化的布局代码。家族契约的意图排列模型不同词汇表唯一契约开宗明义地定义了自己的 Intent开发者应该能用同一种小词汇表来排列任意内容一维或二维、居中它、设置布局盒尺寸、表达空间关系。选择 Stack、Grid 还是 Center 改变的是排列模型arrangement model而不是引入第二套间距刻度或给共享 prop 名赋予新含义。这一原则落实到组件层面意味着gap、padding、width、height在七个成员间遵循同一套数值语义SpacingStep/SizeValue轴的语义main/cross axis由当前成员的排列模型解析而不是各组件自行发明间距刻度永远只有一套来自主题 token而不是每个组件复制一份魔法数字。从源码看这一意图被严格执行Stack 的 gap 样式、Grid 的 gap/rowGap/columnGap 样式都指向同一个spacingVarstoken 表见 stack.stylex.ts 与 Grid.tsx数值经过SpacingStep类型约束后映射到--spacing-*变量而非各写各的像素值。成员规则与边界什么算布局原语成员规则Membership rule契约用公共职责primary public purpose而非实现机制来判定归属一个组件属于该家族当且仅当它的主要公共用途是通过共享的布局原语词汇表排列任意子内容一个修饰器modifier归属于其父组件当它改变某个子元素在该排列中的参与方式。成员Members成员职责Stack一维排列方向、换行、主轴/交叉轴对齐HStack / VStackStack 契约的固定方向形式StackItem修改 Stack 中单个子元素的参与方式Grid二维轨道排列固定列与固有/流式列GridSpan修改 Grid 中单个子元素的行、列跨距Center沿单轴或双轴居中协作者Collaborators共享的SpacingStep、SizeValue类型Stack 与 StackItem 的样式工具函数padding.stylex.tscontainer-padding 架构docs/architecture/container-padding.md组件的.doc.mjs主题元数据与运行时themeProps()发射。明确排除ExcludedSection、Layout 及其命名区域、Toolbar 拥有的是结构区域契约而非本家族的任意子内容排列词汇。Layout 虽是一般用途原语但其公共模型是五个命名槽位而非不受限的子元素排列FormLayout 负责字段排列与表单级可选性AspectRatio 约束单个子元素的盒子而非排列任意子元素Card 与 Dialog 是表面surfaceAppShell 拥有页面外壳与应用导航组合。关键判据一个组件不会仅仅因为源码里用了 flexbox 或 grid 就加入家族。成员资格跟随公共责任不跟随实现机制。共享负责人Shared owner契约为每个共享概念指定了唯一负责人避免多个组件各自解释同一概念概念负责人数字间距词汇表gap/padding 的取值SpacingStep类型盒子尺寸契约数字像素字符串CSS 值SizeValue类型一维方向、换行、主轴/交叉轴对齐StackHStack/VStack 为固定方向形式固定列与固有/流式轨道构造GridGridSpan 负责单个子元素的行列参与单轴或双轴居中Centerbleed 几何container inset 协议architecture:container-padding本地应用 padding 本身不发布该协议核心概念表七个成员的公共语义概念取值/状态默认语义稳定性spacing step00.511.523456810受 token 支持的间距值用于 gap/paddingcurrentbox size数字或 CSS 值字符串数字按像素处理字符串原样透传为 CSS 值currentflow directionhorizontal / verticalStack 默认 verticalHStack/VStack 固定方向currentalignmentmain/cross axis或物理横/纵别名由当前排列模型解析轴currentgap支持处为 uniform、row、column排列项之间的间距不是容器内边距currentpadding支持处为 uniform、axis、logical edge成员自身盒子内部的空间currentitem participationintrinsic、fill、self-aligned、column span、row span修饰器改变单个子元素在父排列中的角色currentresponsivenessintrinsic、wrapping、consumer-authored各成员自文档化自己的可用机制component-ownedSpacingStep 的像素映射见 utils/types.ts00px、0.52px、14px、1.56px、28px、312px、416px、520px、624px、832px、1040px分别对应--spacing-0…--spacing-10主题 token。这就是只有一个间距刻度的落地形式无论你在 Stack 还是 Grid 上写gap{4}得到的都是同一个 16px 的 token 值。SizeValue 契约type SizeValue number | string。数字渲染为像素${width}px字符串原样透传如100%、50vh。Stack 的width/height/maxWidth/minHeight与 Grid、Center 的同名尺寸 prop 全部遵循此契约见 Stack.tsx 的 sizingStyle 构造。跨组件不变量 FR1–FR9共享语义的硬约束契约定义了九条跨组件不变量它们是共享词汇表的执法依据FR1 — 共享名字保持共享值语义。任何被类型化为SpacingStep或SizeValue的成员 prop 都遵循共享的值与强转契约成员只暴露其排列模型支持的能力。例如 Stack 暴露 gap一维 spacingGrid 暴露 gap/rowGap/columnGap二维 spacing但都不会突然改变这些 prop 的含义。FR2 — 逻辑间距跟随书写方向。inline-start/end 是逻辑边缘成员不得将其重新解释为固定的 left/right。Center 与 Stack 的 padding 阶梯使用paddingInlineStart/End、paddingBlockStart/End逻辑属性见 padding.stylex.tsRTL 下自动镜像。FR3 — padding 优先级按边计算。在拥有完整 padding 阶梯的成员上显式的边缘值 该轴的值 uniformpadding覆盖只改变该边。Stack 与 Center 的解析顺序都是edge ?? axis ?? paddingStack.tsx、Center.tsx。FR4 — gap 与 padding 保持分离。gap 分隔排列项padding 内缩容器内内容。Grid 的rowGap/columnGap只覆盖其所在轴的 uniform gap。FR5 — 对齐跟随排列模型。Stack 依据方向解析 main/cross 轴Grid 在轨道中对齐项目Center 控制哪个轴或双轴被居中。当含义不同时一个成员不得复制另一个成员的 prop。例如 HStack 的hAlign是主轴justify-content而 Stack 的hAlign在 vertical 方向下是交叉轴align-items——这正是契约要求含义必须由排列模型解析的原因。FR6 — 修饰器组件要求其父模型。StackItem 控制 Stack 中的参与GridSpan 控制 Grid 中的参与。它们各自的组件契约定义了父模型之外的行为。FR7 — 响应式行为是显式的。Grid 可能因固有轨道数学而回流Stack 仅在配置wrap时换行Center 不创建断点。家族不承诺共享断点或自动区域切换。FR8 — 本地 padding 不是 bleed 信号。Stack 与 Center 当前应用 padding 时不发布容器 inset 几何。后代组件只有在architecture:container-padding命名的发布者下才能依赖 bleed 补偿。FR9 — 公共契约不规定源码结构。组件可以使用成员工具函数也可以直接用平台布局只要可观察的 API 与行为保持正确。成员逐个深入从 props 到 StyleX 实现Stack一维排列的唯一入口Stack.tsx 是一个统一的一维排列组件用direction取代了以往 HStack/VStack 各自为政的实现默认directionvertical。核心 propsStack directionvertical // horizontal | vertical默认 vertical gap{2} // SpacingStep0–10 hAligncenter // 主轴对齐horizontal 下 justify-content vAlignstretch // 交叉轴对齐horizontal 下 align-items justifybetween // 主轴别名镜像 CSS justify-content aligncenter // 交叉轴别名镜像 CSS align-items wrapwrap // nowrap | wrap | wrap-reverse padding{4} // SpacingStep paddingInline{3} // 覆盖 padding 的 inline 轴 paddingInlineStart{2} // 覆盖 inline 起始边逻辑边 width{320} // SizeValue数字px height100% // SizeValue字符串原样透传 isScrollable // overflow: auto asul // 多态渲染 liItem/li /Stack关键实现细节对齐映射hAlign/vAlign依据direction自动解析到justify-content主轴或align-items交叉轴。主轴取值start | center | end | between | around | evenly交叉轴取值start | center | end | stretch见 stack.stylex.ts。justify/align 别名justify镜像 CSSjustify-content、align镜像align-items与 Tailwind 的justify-*/items-*语义对齐降低迁移心智负担。尺寸数字→px、字符串→透传全部走 inline style。运行时主题发射组件调用themeProps(stack, {direction, gap, wrap})将视觉 prop 反射为data-*属性与稳定类名见 themeProps.ts 的 kebab-case 转换direction→data-direction。底层样式由stack()工具函数生成stack.stylex.ts它接受{direction, crossAlign, mainAlign, gap, wrap}返回 StyleX 样式数组这也是实现机制可替换FR9的体现——其他组件可以直接调用stack()而不经过 Stack 组件本身。HStack / VStack固定方向的便捷形式HStack 是 Stack 的极薄包装HStack.tsxexport function HStack({ref, justify, align, hAlign, vAlign, ...props}: HStackProps) { return Stack {...props} directionhorizontal {.../* 别名映射 */} /; }HStackProps通过OmitStackProps, direction | hAlign | vAlign收窄了类型hAlign收敛为StackMainAlignment主轴justify-contentvAlign收敛为StackCrossAlignment交叉轴align-items从类型层面保证固定方向下的合法取值。VStack 同理固定为directionvertical。使用建议如果方向是永不改变的布局事实用 HStack/VStack 让意图自文档化并享受更窄的类型如果方向可能随响应式策略切换直接用Stack direction{...}。StackItem单个子元素的参与控制StackItem.tsx 是 Stack 的修饰器提供两个能力Stack directionvertical gap{2} StackItem sizestaticLogo/StackItem {/* flexGrow:0, flexShrink:0固有尺寸 */} StackItem sizefillContent/StackItem {/* flexGrow:1吃掉剩余空间 */} StackItem sizefill isScrollableScroll region/StackItem StackItem crossAlignSelfcenterMe/StackItem {/* align-self 覆盖 */} /Stack底层stackItem.stylex.ts有两个值得注意的设计flex min 尺寸重置minHeight: 0; minWidth: 0总是被应用。flex 子项隐式min-size: auto内容多时永不收缩这个重置让子项可以被 flex 父容器约束并变得可滚动。sizefillisScrollable构成完整滚动区域fill 提供flexGrow: 1isScrollable 提供overflow: auto加上 min 尺寸重置一个StackItem sizefill isScrollable就能生长填满 Stack 并滚动自身溢出无需额外样式管道。Stack 的isScrollable文档也明确建议当 Stack 本身作为 flex 子项需要滚动时外层配StackItem sizefill isScrollableStack.tsx。Grid固定列与固有/流式轨道Grid.tsx 提供 CSS Grid 布局columnsprop 有两种形态// 固定等宽列 Grid columns{3} gap{4} divA/divdivB/divdivC/div /Grid // 固有/流式列基于最小子宽度的响应式 Grid columns{{minWidth: 280}} {/* auto-fill宽度一致 */} Grid columns{{minWidth: 280, repeat: fit}} {/* auto-fit折叠空轨 */} Grid columns{{minWidth: 280, max: 4}} {/* 上限 4 列仍有 1fr 弹性 */} // 轴级 gap 覆盖 Grid columns{3} gap{2} rowGap{3} columnGap{4} / // 对齐默认 stretch Grid columns{3} aligncenter justifystart / // 瀑布流式行高 GridSpan Grid columns{3} rowHeight{80} gap{3} GridSpan rows{4}Tall/GridSpan GridSpan rows{2}Short/GridSpan /Grid值得展开的实现细节max上限列数的数学buildCappedTemplateGrid.tsx把上限放在轨道的min尺寸上每轨至少(100% - (max-1)*gap) / max因此永远不会超过max列而轨道 max 保持1fr所以当实际列数少于上限时尤其移动端单列现有列仍会拉伸填满整行——右侧不留死区。轨道 min 是max(minWidth, perColumn)再包一层min(100%, …)保证窄视口下单列收缩到容器而不溢出。动态轨道走 CSS 变量而非 inline stylegrid-template-columns: var(--x)通过 StyleX 动态样式生成Grid.tsx这样消费方xstyle覆盖——包括media内的覆盖——仍能生效原始 inlinegrid-template-columns会压过任何类。gap 三件套gap同时写 rowcolumnrowGap/columnGap只覆盖对应轴且全部映射 spacing token。GridSpan行列跨距修饰器GridSpan.tsx 是 Grid 的修饰器Grid columns{3} gap{4} GridSpan columns{2}Wide/GridSpan {/* grid-column: span 2 */} divNormal/div GridSpan columnsfullFull row/GridSpan {/* grid-column: 1 / -1 */} GridSpan rows{2}Tall/GridSpan {/* grid-row: span 2 */} /Grid它的基样式自带minWidth: 0防溢出、display: grid、height: 100%让内容填满所跨的格子。themeProps(grid-span)只发射稳定类名不携带视觉数据。Center单轴或双轴居中Center.tsx 用 flex 实现居中axisprop 控制居中范围Center width{300} height{200} {/* 双轴居中 */} Content / /Center Center axishorizontal…/Center {/* 仅主轴/inline 轴 */} Center axisvertical…/Center {/* 仅交叉轴/block 轴 */} Center isInline 文本/图标行内居中/Center {/* inline-flex */} Center padding{4}…/Center {/* 与 Stack 相同的 padding 阶梯 */}实现要点both同时应用alignItems: center与justifyContent: centerhorizontal只应用 justify-contentvertical只应用 align-itemsCenter.tsx。契约代表矩阵中明确提示居中可观察的前提是轴上存在可用尺寸。Center不设置尺寸时其盒子由内容撑开居中效果不可见需要配合width/height/maxWidth或父容器的约束。尺寸 propwidth/height/maxWidth/minHeight通过动态样式一次函数化生成dynamicStyles.sizingpadding 阶梯与 Stack 完全一致edge ?? axis ?? padding。允许的组件差异 AV1–AV7统一中的多样性契约在共享语义之上明确允许成员保留自己的特征防止统一演变成趋同编号差异维度内容AV1排列模型Stack 一维流、Grid 二维轨道、Center 单/双轴对齐AV2可用 propsStack/HStack/VStack/Center 暴露完整逻辑边 padding 阶梯Grid 暴露轴级 gap修饰器暴露父专属参与方式而非盒级布局控制AV3元素所有权Stack/HStack/VStack/StackItem 多态aspropGrid/GridSpan/Center 当前拥有固定 div 元素AV4溢出Stack/HStack/VStack/StackItem 暴露各自当前滚动行为isScrollableGrid 与 Center 不因家族成员身份获得该能力AV5响应式Grid 固有列、Stack 换行、消费方自写响应式样式保持组件专属AV6实现允许裸 flex/grid 与共享工具函数并存家族成员身份不制造实现级迁移债AV7主题化各组件的.doc.mjs元数据文档化已发布的 targets 与能力运行时themeProps()发射它们跨组件规则由architecture:component-theming-surface拥有关于 AV3 有一个易混淆点Stack 与 StackItem 的多态能力来自asprop默认div例如Stack asul渲染为列表而 Grid/GridSpan/Center 当前固定渲染div。代表矩阵成员状态 × 共享不变量 × 刻意差异成员与状态共享不变量刻意差异Stack / vertical 或 horizontal共享 spacing 与 sizing 值方向解析对齐轴可换行、可滚动、可渲染指定元素HStack 或 VStack同一 Stack 契约 固定方向更窄的对齐类型匹配固定轴StackItem /sizefill单个子元素可消耗 Stack 剩余空间isScrollable与 StackItem 的 flex min 重置配对Grid / 固定列共享 size 与 gap 值显式等宽轨道数Grid / 固有列共享 size 与 gap 值minWidth、可选列数上限、fill/fit 构造流式轨道GridSpan / columns 或 rows修饰器参与 Grid跨轨道而非控制父几何Center / 单轴或双轴共享 size 与 padding 值某轴居中可观察前需要可用尺寸实战组合把七个成员拼成一致布局以下是一个体现同一个小词汇表的页面骨架示例import {Stack, HStack, VStack, StackItem, Grid, GridSpan, Center} from astryxdesign/core; export function Dashboard() { return ( VStack gap{5} {/* 顶部栏固定尺寸 Logo 弹性内容 固定操作区 */} HStack gap{3} vAligncenter padding{4} isScrollable StackItem sizestaticLogo //StackItem StackItem sizefillSearchBar //StackItem StackItem sizestaticAvatar //StackItem /HStack {/* 主区域响应式网格最大 4 列移动端单列拉伸全宽 */} Grid columns{{minWidth: 280, max: 4}} gap{4} GridSpan columns{2}SummaryCard //GridSpan GridSpan columnsfullTrendChart //GridSpan StatCard /StatCard /StatCard / /Grid {/* 底部空态双轴居中并让内容垂直充满 */} Center height{240} padding{6} VStack gap{2} hAligncenter EmptyStateIcon / pNo data yet/p /VStack /Center /VStack ); }这个例子覆盖了全部七个成员且每处 spacing 都来自同一张SpacingStep表顶部栏gap{3}是 12px主网格gap{4}是 16px外层gap{5}是 20px——数值即 token无需换算。采用情况、已知缺口与变更耦合采用与例外组件/关注点采用情况当前缺口或例外Stack, HStack, VStack共享一维契约gap token 映射与 Grid 的映射分开实现StackItem共享修饰器契约家族级 flex-item grow/shrink/basis 词汇表不存在Grid, GridSpan共享二维契约没有跨组件测试证明 Grid 与 Stack 对相同 gap step 解析出相等的值Center共享 sizing 与 padding 词汇与 Stack 相同padding 是局部的、不发布 bleed 几何共享验证各组件单元覆盖没有 computed-style 矩阵证明每个共享 spacing/sizing 值跨成员一致契约明确声明这些是已发布的覆盖或采用缺口而不是授权在文档 PR 中加 props 或改布局行为的许可。变更耦合Change coupling契约规定了变更必须联动审查的边界这直接指导贡献者增加或修改共享空间 prop需检查其名称、值类型、强转、逻辑方向、优先级是否仍与本家族一致修改SpacingStep或SizeValue需审查所有暴露该类型的成员并更新代表性的跨组件证据修改 Stack 的方向/对齐HStack 与 VStack 必须在同一次审查中同步更新修改 Grid 的固有轨道构造需保留其文档化的 fixed、fill、fit、capped 四种状态并有聚焦测试将成员纳入 container bleed 是独立的architecture:container-padding变更需附带渲染兼容性证据新组件加入的唯一条件是公共责任满足成员规则——使用 flexbox、grid 或共享工具函数不足以为凭。决策记录DEC-1契约收录了一条已批准的决策2026-08-30决策人cixzhang组合原语与结构区域有各自的所有者。Stack、Grid、Center 及其修饰器组成 layout-primitives 家族Section、Layout 区域与 Toolbar 有独立的结构区域所有者。这让成员资格可预测原语规则描述任意子内容的组合而不把槽位、表面或工具栏语义塞进同一契约。验证地图不变量如何被测试契约提供了逐条不变量的验证地图同时坦诚标注了已证明与缺失证据契约验证方式证据证明的内容缺失证据FR1, FR3Stack/Center 源码 padding 类集测试当前合并顺序edge axis uniform等价写法产出等价类集无浏览器矩阵比较跨成员的计算值及两种书写方向FR2Stack/Center 的逻辑属性源码声明实现用 inline-start/end 而非物理 left/right当前测试未在 LTR 与 RTL 下渲染 padding 阶梯FR4Stack/Grid 源码 本地测试API 保持 gap 与 padding 分离Grid 接受 uniform 与轴级 gap propsGrid gap 测试只断言渲染成功而非计算间距无测试将 step 与 Stack 比较FR5Stack/Grid/Center 源码 本地渲染测试每个成员当前都通过自己的排列模型路由对齐Stack/Grid 测试未断言接受值的计算对齐FR6StackItem/GridSpan 源码 本地测试sizefill映射为 fill 样式滚动改变类输出GridSpan 断言精确的 inline 行列跨距StackItem 的 fill 测试只断言渲染内容无集成测试证明修饰器覆盖父组件的每种状态FR7Grid 精确轨道输出测试 Stack/Center 源码Grid 的 fixed/intrinsic 轨道字符串被固定Stack 仅在配置时换行Center 无断点路径无跨成员响应式集成矩阵FR8对照architecture:container-padding的源码审查Stack/Center 应用本地 padding 而不发布容器 inset 变量无浏览器断言证明 Divider/Table 后代的非 bleed 行为对应的测试文件即契约 front matter 中的verified_by列表Stack.test.tsx、StackItem.test.tsx、Grid.test.tsx、Center.test.tsx。契约的诚实表述值得关注测试是组件局部的且若干测试只断言渲染成功或类变化并不证明计算后的 gap、对齐、逻辑方向或跨组件一致性——那些被点名列为验证缺口而非隐式覆盖。这意味着贡献者补测试的空间是明确标出的。内容边界这份契约不做什么最后契约划定了自己的内容边界防止被误读为组件总规范不重复组件的 prop 表请读各成员的.spec.md如 Stack.spec.md、Grid.spec.md、Center.spec.md不规定实现机制不定义结构区域那是 Section/Layout/Toolbar 的家族职责不指派响应式断点不拥有主题 anatomy 与 targets属architecture:component-theming-surface。当前契约没有未决问题Open questions: None——采用表列出的缺失能力与验证缺口是待办事实不是悬而未决的家族政策。小结Astryx 的 layout-primitives 家族把排列任意内容这件最简单也最容易被做乱的事收敛为一份可审查、可验证、可演进的契约成员资格看公共职责共享词汇由SpacingStep/SizeValue与排列模型唯一解释九条不变量 七条允许差异 一张验证地图共同保证——无论是用 Stack 排一维、Grid 排二维、Center 居中还是用 StackItem/GridSpan 微调子元素写出来的代码在间距、对齐、逻辑方向、padding 优先级上都说着同一种语言。这份契约的完整文本与所有相关决策记录可在 docs/families/layout-primitives.md 及其引用的架构文档container-padding、public-component-api、component-theming-surface中继续追溯。【免费下载链接】astryxAn open source design system thats fully customizable and agent ready项目地址: https://gitcode.com/GitHub_Trending/as/astryx创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考