尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

轻量级CSS框架Madeira实战:设计令牌、容器查询与暗色主题

轻量级CSS框架Madeira实战:设计令牌、容器查询与暗色主题 昨天整理项目代码顺便把这套我自己维护了大半年的CSS框架重新梳理了一遍决定写成一篇完整的实操记录分享出来。它叫Madeira一个极简的、以设计令牌为核心的轻量级CSS框架整包gzip后不到20KB没有运行时依赖不锁定任何前端技术栈。当初做它的动机很简单市面上主流的UI框架要么太重要么学习成本偏高要么默认风格一眼就能认出模板味而我需要的只是一个能快速落地、能按品牌定制、又不会在开发到一半时逼我写一堆覆盖样式的方案。这篇文章不谈宏观趋势纯粹是项目实战从设计语言怎么定、组件怎么实现、主题切换怎么做到发布打包和踩坑记录适合正在自己搭建UI体系、或者在小团队里做前端基础设施的人参考。1. 为什么我会动手写一个“小而稳”的CSS框架1.1 框架疲劳以及减法背后的真实需求在开始写Madeira之前我的状态可以用一个词形容框架疲劳。Bootstrap功能全但每次接新项目都要花时间调主题变量、剔除不需要的组件、忍受默认字体和间距带来的“网站感”。Tailwind解决了一部分问题但utility-first要求团队每个人都知道类名约定项目代码里很容易出现十几个class拼在一起、业务逻辑和样式耦合得越来越紧的情况。自己从零写一套样式吧又容易陷入另一个深渊没有完善的变量体系、没有组件状态管理、写到后面连自己都分不清某个类名是干嘛的。于是我给Madeira定了三条硬性原则第一所有组件的外观都通过CSS变量暴露变量是唯一的配置入口框架本身不做复杂的主题编译步骤第二全局只用一个类名命名空间md-不嵌套、不串联组件之间不互相依赖删掉任何一个组件CSS都不会影响其他组件第三禁用!important所有优先级交给级联和layer去管理。这三点定下来之后后面所有实现都变得简单了类名就是接口变量就是配置。为什么非要在2024年自己维护一个框架说白了独立开发者和小团队真正需要的不是又大又全的组件库而是一套能跟着产品一起成长的表达系统。产品早期需要快速出界面中期要统一视觉规范后期要支持暗色模式和主题定制用Bootstrap这类大而全的方案前期很快中后期总在跟框架的默认值对抗用Tailwind前期写起来很爽后期视觉规范往往散落在HTML里改起来难受。Madeira走的是中间路线类名不多但每个组件都有清晰的状态和变体变量不少但都有明确的语义命名改一版主题基本不用碰组件样式文件。1.2 从“马德拉木”谈起一个名字如何影响设计取舍给框架起名的时候我特意选了Madeira这个词。它有几个层面的含义马德拉群岛以出产稳定的强化葡萄酒闻名马德拉木则是一种纹理细腻、质地稳定、常用于高端乐器和家具的木材。这两个意象正好对应我对这套框架的期待稳定、耐久、有质感不希望它看起来很“科技感”或者很“廉价模板”。这个命名直接影响了颜色和形状的选择。主色不是常见的蓝色系而是偏暖的墨绿色接近马德拉木经过氧化后的深色调辅助色用古铜色而不是亮橙色或者金色背景使用带一点暖意的米白而不是纯白。这些决策会贯穿整个设计令牌的定义。很多人写框架时一上来就抄Material Design的调色板这不是不行但会让产品缺乏辨识度。Madeira在视觉上想要的是第一眼看起来舒服看久了不腻放到不同业务场景里都不会太抢戏。我在初始设计文档里写了一句大白话“每个组件都应该像一块用了很久的木头表面有温度但结构稳定。”这句话听起来很虚但它帮我做了很多技术取舍。比如圆角不能过于夸张否则会显得卡通阴影不能太重尽量用边框加微弱阴影来分层间距要给足呼吸感但格式塔原则摆在那相邻元素的分组关系必须清晰。视觉细节我会在第2章用具体数值展开这里想说的是一个项目的名字如果能成为设计决策的锚点它就不只是一个代号。2. 设计令牌从比例到组件的统一语言2.1 颜色与字号的“比例思维”设计令牌是Madeira最核心的资产。很多人写组件库跳过令牌直接写组件结果就是按钮一个蓝色、链接一个蓝色、标签又一个蓝色最后看起来总差一口气。正确顺序应该是先把令牌定义清楚再让组件去引用令牌。颜色方面我最终定了一组很克制的色板核心变量长这样:root { --md-bg: #f8f5ef; --md-surface: #ffffff; --md-surface-alt: #efead9; --md-text: #262b27; --md-text-muted: #6b746e; --md-border: #d9d2c4; --md-primary: #3f5b4b; --md-primary-hover: #2f4a3b; --md-primary-active: #243c30; --md-accent: #b58a5f; --md-danger: #b33a3a; --md-success: #3d7a53; --md-warning: #c08a2d; }为什么主色用墨绿而不是更常见的蓝紫色我做过一组对比测试在同样的米白背景下墨绿色作为主操作色视觉重量比同亮度的蓝色更轻长时间阅读时眼睛不容易疲劳搭配古铜色辅助色整体色温偏暖比冷色调的蓝紫组合更适合内容型产品。当然这只是适合我场景的选择不代表蓝色不好重要的是这套变量可以整个替换掉。你只要把--md-primary换掉全站按钮、链接、选中态、焦点环都会跟着变这就是令牌的意义。字号体系我同样没有用随机数列。最终采取的近似1.25比例:root { --md-text-xs: 0.75rem; /* 12px */ --md-text-sm: 0.875rem; /* 14px */ --md-text-base: 1rem; /* 16px */ --md-text-lg: 1.125rem; /* 18px */ --md-text-xl: 1.375rem; /* 22px */ --md-text-2xl: 1.75rem; /* 28px */ }为什么从12px开始而不是14px因为大量场景下需要比正文更小的注释文字且12px在绝大多数屏幕上依然处于可读范围内。为什么正文定16px因为大多数浏览器的默认字号就是16px尊重用户浏览器设置是底线。标题层级没有用到h1的尺寸实际使用时可以根据产品调但每一级之间的倍率保持在1.25上下视觉上才有节奏感。这套字号和颜色组合起来页面即使没有任何装饰性元素也不会显得单调。2.2 间距、圆角、阴影以及我为什么不全靠屏幕宽度做响应式间距这块我用了“4的倍数加少量例外”的策略。4的倍数好记也好对齐但全部用4的倍数会让页面显得死板所以我在大间距上混入了基于黄金比例的额外档位:root { --md-space-1: 0.25rem; /* 4px */ --md-space-2: 0.5rem; /* 8px */ --md-space-3: 0.75rem; /* 12px */ --md-space-4: 1rem; /* 16px */ --md-space-5: 1.5rem; /* 24px */ --md-space-6: 2rem; /* 32px */ --md-space-7: 3rem; /* 48px */ --md-space-8: 4.5rem; /* 72px */ }圆角和阴影分别是:root { --md-radius-sm: 4px; --md-radius-md: 8px; --md-radius-lg: 14px; --md-shadow-sm: 0 1px 2px rgba(38, 43, 39, 0.06), 0 1px 3px rgba(38, 43, 39, 0.08); --md-shadow-md: 0 4px 12px rgba(38, 43, 39, 0.08); }注意我在阴影里使用的都是低透明度的黑色而不是灰色。黑色透明度阴影在彩色背景上的表现更自然不会有灰得发脏的问题。圆角取值为什么是4/8/14而不是4/8/12因为14px和8px之间的差异更明显卡片和按钮能拉开层次同时14px在元素圆心角的角度上接近自然纸张的感觉不会显得工业感太强。响应式策略大概是这套框架里最值得聊的部分。传统媒体查询只以视口宽度为条件但组件的上下文才是真正的变量同一个卡片组件放到侧边栏和主内容区宽度完全不同如果只能靠视口断点去调整就需要根据它在页面里的位置写额外类名覆盖。我这里做了两层处理页面骨架仍使用常规媒体查询断点640px、980px、1280px负责整页布局的切换组件内部则使用容器查询让组件根据自身容器的宽度调整内部布局。容器查询的写法很简单.card { container-type: inline-size; } container (min-width: 420px) { .card__meta { flex-direction: row; } }这个方案让组件真正做到“上下文自适应”。当然容器查询在Safari 15及以下是不支持的所以我在构建时保留了一层类名回退方案例如.card--wide .card__meta。现代浏览器走容器查询老浏览器走类名控制。后面我会说这个方案维护成本几何但结论是值得的它让组件封装性上了一个台阶。3. 核心组件实现实录从按钮到导航3.1 按钮状态管理不是只换颜色按钮是几乎每个项目都会碰到的组件也是状态最多的基础组件。我见过很多按钮实现只写了默认态和hover态disabled直接opacity: 0.5结果键盘用户根本不知道这个按钮是否可聚焦屏幕阅读器也读不出按钮的状态。Madeira里按钮的状态矩阵是六边形默认、hover、active、focus-visible、disabled、loading这六个状态都要有明确的视觉表达而且不能互相覆盖。先看结构button typebutton classbtn btn--primary span classbtn__label保存/span /button再看关键CSS.btn { --btn-px: 1.1em; --btn-py: 0.45em; display: inline-flex; align-items: center; justify-content: center; gap: 0.5em; padding: var(--btn-py) var(--btn-px); border: 1px solid transparent; border-radius: var(--md-radius-sm); font: inherit; font-weight: 600; cursor: pointer; transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease; } .btn--primary { background: var(--md-primary); color: #fff; } .btn--primary:hover { background: var(--md-primary-hover); } .btn--primary:active { background: var(--md-primary-active); } .btn:focus-visible { outline: 2px solid var(--md-primary); outline-offset: 2px; } .btn[disabled] { opacity: 0.55; cursor: not-allowed; } .btn.is-loading { cursor: wait; } .btn.is-loading .btn__label { visibility: hidden; } .btn.is-loading::after { content: ; position: absolute; width: 1em; height: 1em; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: md-spin 0.7s linear infinite; }几个细节值得展开。padding用em而不是rem理由很简单按钮的横向内边距应该随着按钮自身字号缩放。如果按钮有一个大尺寸变体font-size: 1.125rem它应该整体变大而不仅仅是文字变大用em作为padding单位就能自然达成这个效果。is-loading的实现我没有隐藏按钮而是把文字visibility: hidden再用伪元素做旋转的加载圈这样按钮宽度不变不会因为文案消失而抖动。disabled没有用pointer-events: none因为那样会让鼠标事件全部失效屏幕阅读器也可能无法正确告知用户状态保留disabled属性并设置cursor: not-allowed就够了。还有一个细节是:focus-visible。只写:focus会让鼠标点击的元素也出现焦点环视觉上比较吵只写:focus-visible又可能在老浏览器上失效。所以我把基础焦点环写在:focus上然后用:focus-visible覆盖现代浏览器只会在键盘导航时展示老浏览器回退为总有焦点环可访问性不会丢。3.2 表单控件原生样式的对抗与协同表单是另一个“看起来简单、做起来全是细节”的组件。我的目标不是重构原生控件而是让原生控件在Madeira的语境里看起来不违和同时保持键盘操作和屏幕阅读器体验。全局样式先统一了一遍input, textarea, select { width: 100%; padding: 0.55em 0.8em; font: inherit; color: var(--md-text); background: var(--md-surface); border: 1px solid var(--md-border); border-radius: var(--md-radius-sm); } input:focus-visible, textarea:focus-visible, select:focus-visible { outline: 2px solid var(--md-primary); outline-offset: 1px; }textarea处理了min-height和resize的关系select处理了下拉箭头的兼容。实际踩坑主要在两个地方。第一个坑是Chrome的自动填充背景色。暗色模式下autofill后的输入框会变成刺眼的淡黄色或者淡蓝色怎么都去不掉。解决方案是用内阴影“涂满”输入框背景input:-webkit-autofill, input:-webkit-autofill:hover, input:-webkit-autofill:focus { box-shadow: inset 0 0 0 1000px var(--md-surface); -webkit-text-fill-color: var(--md-text); }这个方案比background-color靠谱因为Chrome会自动覆盖背景色但不会覆盖box-shadow。第二个坑是select在不同平台上的表现差异Windows上的select默认有灰色边框和凸起样式Mac上又是另一个样子。我设置appearance: none然后自己加一个用clip-path裁剪的箭头作为背景图这样在所有平台保持一致。表单的错乱状态一般是通过aria-invalidtrue来标记视觉上给一个红色边框和轻微的红色阴影但更重要的是错误提示文字要放在aria-livepolite的区域里。用户提交表单时屏幕阅读器才能及时读到校验结果否则只变红边框视觉障碍用户根本不知道发生了什么。这个细节在多数组件库里都做得不到位我自己写框架必须从一开始就把它写对。3.3 卡片与媒体对象容器查询的实战落地卡片是容器查询收益最明显的组件。一个卡片在移动端单列显示时头部、内容、底部操作区应该是纵向排列放到桌面侧边栏或者宽屏卡片时头部和时间戳可以横向并排Footer的操作按钮也可以从左对齐变成右对齐。如果不做容器查询就得给每个使用场景写单独的父级类名比如.sidebar .card__footer { flex-direction: row; }既耦合又难维护。Madeira的卡片结构分三块头部、主体、底部article classcard header classcard__header h3 classcard__title文章标题/h3 span classcard__meta2024-11-02/span /header div classcard__body p正文内容/p /div footer classcard__footer a href# classbtn btn--text/a /footer /article对应的容器查询写法.card { container-type: inline-size; padding: var(--md-space-4); background: var(--md-surface); border: 1px solid var(--md-border); border-radius: var(--md-radius-lg); } container (min-width: 360px) { .card__header { display: flex; align-items: baseline; justify-content: space-between; } .card__footer { display: flex; justify-content: flex-end; } }默认状态下卡片头部是自然流排布标题在上、日期在下容器宽度超过360px后头部变成横向排布。这个数字不是随便定的360px大致是卡片从“手机单列”过渡到“侧边栏双列”的临界宽度。你在实际项目里可以根据内容结构调整这个值。媒体对象Media Object也一样头像在左、内容在右是经典布局但当容器宽度很窄时头像和文字挤在一行反而阅读困难所以同样用容器查询在窄容器下切成上下排布。这两类组件组合起来基本覆盖了Feed流、评论列表、个人主页卡片等大部分业务场景。3.4 导航与下拉菜单不需要JavaScript也能做到可用导航菜单的移动端折叠是一个常见的交互。我不太想在一套号称轻量的框架里塞一堆JS交互所以折叠菜单直接用经典方案默认隐藏子菜单容器获得焦点或悬停时展开。.nav__item:focus-within .nav__submenu, .nav__item:hover .nav__submenu { display: block; }:focus-within的好处是键盘用户按Tab移动到子菜单里的某个链接时父级菜单依然保持展开焦点不会“掉”到隐藏元素里。下拉菜单的定位用纯CSS的话需要给子菜单设置固定或绝对定位并且要处理溢出问题。我的建议是如果需要子菜单跟随视口滚动、需要支持按方向键在菜单项之间移动那还是引入少量JavaScript比较合理。Madeira里我写了一个很小的Dropdown组件只有两个方法open和close内部用aria-expanded标记展开状态不依赖任何框架。发布时打包成单独的JS文件用户只需要引用即可。这套导航的另一个细节是移动端的汉堡按钮。很多人直接用三个span画线条但更好的是用一张内联SVG作为背景图加上aria-label打开菜单保证屏幕阅读器能读出来。展开和收起时切换aria-expanded并确保菜单容器在收起状态使用display: none而不是visibility: hidden加height: 0后者只能藏住视觉键盘焦点还是能钻进去。4. 暗色主题、打包发布与文档站点4.1 暗色主题的“零闪烁”方案暗色主题是现在用户越来越依赖的功能写起来也不难难在切换时不要让页面闪白。我在Madeira里用了最稳妥的方案在页面head区域直接内联一段极小的脚本优先于所有CSS执行读取localStorage里的主题偏好如果有则立刻给挂上data-themedark然后CSS变量根据这个属性换一套值。script (function () { var theme localStorage.getItem(md-theme); var prefersDark window.matchMedia((prefers-color-scheme: dark)).matches; if (theme dark || (!theme prefersDark)) { document.documentElement.setAttribute(data-theme, dark); } })(); /script这段脚本必须在CSS加载之前执行才能避免闪烁。主题切换按钮的JS逻辑就负责两件事更新localStorage以及同步更新上的data-theme属性。变量覆盖写法如下:root[data-themedark] { --md-bg: #1e211f; --md-surface: #282d29; --md-surface-alt: #333a35; --md-text: #e6e8e4; --md-text-muted: #9aa29c; --md-border: #3d453f; --md-primary: #6ba587; --md-primary-hover: #82b89c; --md-primary-active: #519a73; }这里的关键思路是暗色模式不是每个组件单独出一套样式而是整体替换令牌所有组件因为已经引用了令牌自动跟随。为了原生表单控件和滚动条也跟随还要加上:root[data-themedark] { color-scheme: dark; }color-scheme这个属性作用很大它让浏览器原生的滚动条、select下拉控件、日期选择器都切换成暗色表现不需要自己重新画。之前看到不少组件库没写这一行暗色模式下用户打开select还是一大块白体验一下就露馅了。4.2 Vite构建ESM和IIFE双格式发布发布这套框架时我一直在想怎么输出才能让不同项目都方便使用。只打包一个dist/madeira.jsVue项目里用import原生页面里用script标签TypeScript项目里还得有类型声明每个方式都需要不同格式。最终决定用Vite的库模式一次构建产出多种格式。// vite.config.js export default { build: { lib: { entry: src/index.js, name: Madeira, fileName: (format) madeira.${format}.js, formats: [es, iife] }, cssCodeSplit: false, sourcemap: true, minify: esbuild } }formats里写的是[es, iife]es面向现代打包工具iife面向直接script引入的页面。sizeLimit之后还会配一个自动检查gzip体积的脚本超过某个阈值就报警告防止有人不小心把大依赖混进来。CSS的处理比较重要我特意把框架CSS拆成多个小文件放src/styles/但构建时合并成单个dist/madeira.css输出不把CSS打进JS。原因是大多数组件库的JS只负责交互逻辑样式应该独立加载这样Vue或React项目可以用import madeira/dist/madeira.cssCDN页面就放一个link标签。同时JS里只保留极小的运行时逻辑不会出现“为了用下拉菜单被迫加载几KB无关JS”的情况。4.3 文档站怎么组织才不白做做开源工具类项目文档站比代码本身更能决定别人是否愿意用。但没必要一上来就套一个重型文档框架我用的是最简单的Vite静态站每个组件一个Markdown页面页面结构固定为四段基础示例、尺寸与状态、参数表格、完整代码。参数表格列出所有CSS变量和类名变体完整代码区直接提供可复制的HTML片段。这个组织方式最大的好处是“傻瓜式友好”使用者打开页面就知道这个组件能不能满足需求需要改哪里直接把代码块复制到自己项目里粘上然后改几个变量就能用。我见过一些框架文档做得花里胡哨示例只展示效果不展示代码或者把代码折叠起来藏在三级菜单里这都对采用率有负面影响。文档站还放了一个“全局设计令牌”页面把颜色、字号、间距、圆角、阴影做成一张张对照卡片点击任意卡片可以复制变量名。设计师拿这个页面定规范开发拿这个页面写代码沟通成本能降不少。实测下来新成员接入项目的速度从“读半天源码”缩短到“翻一遍令牌页加组件页”这就是文档价值的最直观体现。5. 实测中踩过的坑与避坑速查表5.1 焦点环与键盘可访问性最容易被忽略的a11y细节做组件库最容易犯的低级错误是把焦点环用outline: none去掉还美其名曰“更干净”。用鼠标操作的人不会注意到焦点环但键盘用户按Tab移动时会完全迷失方向不知道当前焦点在哪里。我自己的项目里曾经因为追求简洁去掉了按钮的焦点环后来用键盘走查一遍才发现整个页面根本无法操作。正确的做法在前面提到过用:focus-visible区分鼠标点击和键盘导航。另有一个配套细节弹窗或者下拉菜单打开后焦点应该被移动到容器内关闭后焦点要能回到触发按钮上。Madeira的Modal组件里我手动维护了一个lastFocusedElement变量打开时保存关闭时调用focus()恢复。这些细节不在视觉稿里但在真实的可用性测试中非常关键。5.2 暗色模式下的原生控件Chrome的“顽固”背景与select箭头暗色模式下Chrome自动填充的问题本文已经给出box-shadow方案但还有一个更隐蔽的场景暗色模式下select组件自带的下拉箭头在某些Linux发行版上会变成深蓝色和整体风格很不搭。我的处理方案是统一appearance: none然后用内联SVG做背景箭头。这里要特别注意SVG的data URI编码简单的#号在URL里要写成%23否则整个背景会被浏览器判定无效。这个坑我当初排查了半小时最后发现就是少写了一个转义字符。5.3 字体加载带来的布局偏移CLS参考系统的字体样式确实简洁但项目里如果需要引入自定义字体就会碰到字体加载前后字形尺寸不一致的问题。Madeira本身不加载任何远程字体但我为接入自定义字体的项目准备了一套推荐设置font-display: swap给body设置明确的line-height: 1.6给标题设置line-height: 1.2。这样字体加载完成后行高不会变化段落和标题的布局偏移量能控制在很小的范围。还有一个小技巧是给标题加text-wrap: balance让标题换行更均匀特别是在卡片列表里这一条对视觉整齐度的提升立竿见影。5.4 样式覆盖优先级如何避免被老代码碾压这是实战中最常遇到的一类问题项目里已经有老样式例如.product-list button { background: red; }它出现在Madeira的CSS之后优先级就会高过.btn的background属性导致按钮变色。为了让用户可控我在框架的reset阶段就引入了layerlayer reset, madeira, project;把这个声明放在所有CSS之前语义是框架自己的reset优先级最低组件样式其次业务项目的覆盖样式优先级最高。这样一来项目里写同名的样式选择器时不需要再去拼优先级也不需要用!important去压外部样式。这个方案在Chrome 99和Firefox 97都能用老浏览器上layer会被当作无效声明跳过不会影响框架的基本表现。下面是一个速查表记录了这套框架里最常踩的5个问题、原因和解决方案问题现象根本原因解决方案按钮点击后焦点环消失只写了:focus没有:focus-visible使用:focus :focus-visible组合暗色模式输入框自动填充发白Chrome强制覆盖背景色box-shadow: inset 0 0 0 1000px var(--md-surface)select箭头在暗色模式下颜色诡异原生箭头不跟随color-schemeappearance: none使用SVG背景箭头卡片在侧栏和主区域表现不一致只依赖视口媒体查询使用容器查询 类名回退老代码覆盖按钮样式选择器优先级冲突使用layer reset, madeira, project声明层级最后一列不是标准答案但都是实战中验证过有效的做法。5.5 体积控制为什么20KB是一个值得守住的红线之前提过Madeira的整体gzip体积控制在20KB以内这其实是一个很保守的目标。为什么把它当红线因为框架一旦超过这个体量它相对于主流UI框架的体积优势就没了。我做体积优化时主要做了三件事第一所有组件只用当前需要的属性不写“预备用”的样式第二将重复的声明提到全局选择器里减少冗余第三用lightningcss压缩产物效果比常规cssnano更好。最后构建完看一眼产物我还会再检查一下是否残留了测试用的花括号和注释保证发出去的包是干净的。有人可能会问为什么不用PostCSS或Sass来写这套框架早期版本确实用过Sass但后来发现CSS变量已经能覆盖几乎所有定制需求Sass的嵌套反而容易让人写出过高的选择器优先级。所以现在的源码直接用原生CSS加CSS变量组织工具链简单Debug也方便。最后分享一点实际使用感受这套框架我在三个不同类型的项目里完整跑过一个内容型新闻站、一个小型管理后台、一个个人项目展示页。最大的感受是它的上限完全取决于设计令牌的质量下层组件的容错率很高。只要颜色、字号、间距这三大令牌定义得合理组件怎么做都不会太难看反过来说如果令牌本身乱掉组件层再怎么调都是补东墙拆西墙。另外还有一个体会就是“少即是多”在组件库领域需要付出额外的坚持。每次接到新需求都想往里加组件但每加一个组件都意味着文档、测试、维护成本的增加。我给自己的原则是同一个交互模式至少出现三次才把它固化成框架组件只出现一两次的就在业务项目里局部实现。这样既保证了框架的通用性也留出了足够的迭代空间。如果你也在考虑给自己或者团队维护一套轻量UI体系我的建议是先别急着写组件花三到五天时间把设计令牌想清楚再从一个按钮和一个卡片开始边用边补。好的框架不是设计出来的是在真实项目里反复打磨出来的。Madeira目前还在持续完善中下一步打算补上时间线组件和更完整的表单校验状态到时候再整理一篇新的实操记录。
返回列表