样式与 Token
framework 通过 Nuxt module 注入一套 CSS 基础层:reset、响应式工具类、设计 Token(CSS 变量),以及可选的暗色主题覆盖。所有 Token 是单一事实源——原生 CSS 的 var(--primary) 与 Tailwind 的 bg-primary 共用同一套底层变量,业务覆盖变量即同时影响两边。
本页讲「有哪些 Token、怎么用、Tailwind 怎么对应」。CSS class 命名 / 单位约束 / 硬编码零容忍等编码规范见 开发规范 · CSS;主题切换(亮 / 暗 / 跟随系统)见 主题系统。
CSS 基础层与注入
定位:基线样式非零样式(framework#210)——swift-web 组件库不是零样式 headless,每个组件默认自带「基本能用」的简洁基线(布局不塌陷 / 不粘连、弹层定位正确、状态反馈可辨);「丑但能用」合格。视觉细节由项目 Token / CSS 覆盖。
framework 随 npm 包发布 9 个样式文件,由 module 按 swiftConfig 注入:
| 文件 | 作用 | 注入条件 |
|---|---|---|
tokens.css | :root 设计 Token(单一事实源)+ 基础元素样式 | 永远注入(无开关) |
reset.css | 精简 reset | styles.reset !== false(默认开) |
responsive.css | 断点 + .sw-* 工具类 + 触摸优化 | styles.responsive !== false(默认开) |
theme.css | 暗色 Token 覆盖(@media + [data-theme]) | theme.mode 为 system / toggle(固定 dark / light 改注入对应 color-scheme 声明) |
color-scheme-dark.css | 声明 color-scheme: dark,让浏览器原生 UI(滚动条 / 表单控件等)跟随固定深色站(深色调色板由 applier 的 overrides.css 提供) | theme.mode 为 dark |
color-scheme-light.css | 声明 color-scheme: light,锁定浏览器原生 UI 为浅色(即使系统处于深色模式也不跟随) | theme.mode 为 light |
modal.css | SwModal 默认皮肤(--sw-modal-* 映射语义 Token) | styles.modal !== false(默认开) |
toast.css | SwToast 默认皮肤(--sw-toast-* 映射语义 Token) | styles.toast !== false(默认开) |
tailwind-preset.css | @theme inline 把 Token 映射到 Tailwind 命名空间 | 启用 Tailwind 时业务侧 @import |
注入顺序:framework 先注入,业务的 overrides.css / custom.css 在 nuxt.config.ts 的 css[] 中后追加,因此能覆盖。开关详见 module options · styles。
Token 清单
所有 Token 定义在 tokens.css 的 :root。业务直接 var(--x) 引用,不写 fallback——:root 默认值已兜底。暗色值在 theme.mode 为 system / toggle 时由 theme.css 覆盖(手动锁定 [data-theme='dark'] 或系统 prefers-color-scheme: dark);固定 dark 模式则由 applier 直接在 :root 定义深色调色板。
颜色
语义命名 + foreground 配对(Shadcn v4 约定):--x 是背景 / 主体色,--x-foreground 是其上的文字色。
| 变量 | 用途 | Light | Dark |
|---|---|---|---|
--background | 页面底色 | #ffffff | #0f172a |
--foreground | 页面主文字 | #1a1a1a | #f1f5f9 |
--foreground-soft | 正文弱化档(介于 --foreground 与 --muted-foreground 之间的第三级文字层) | #4b5563 | #cbd5e1 |
--card | 卡片底色 | #ffffff | #1e293b |
--card-foreground | 卡片内文字 | #1a1a1a | #f1f5f9 |
--popover | 浮层底色 | #ffffff | #1e293b |
--popover-foreground | 浮层文字 | #1a1a1a | #f1f5f9 |
--primary | 主色 | #3b82f6 | #60a5fa |
--primary-foreground | 主色上的文字 | #ffffff | #0f172a |
--gradient-brand | 品牌渐变占位(CTA 类以 background-image: var(--gradient-brand) 消费;默认为两端同为主色的退化渐变 = 视觉纯色,品牌主色是渐变的站点覆盖为真渐变即全站生效) | linear-gradient(135deg, var(--primary), var(--primary)) | 跟随 --primary |
--secondary | 次色背景 | #f3f4f6 | #334155 |
--secondary-foreground | 次色文字 | #1a1a1a | #f1f5f9 |
--accent | 强调(hover / active 底色) | #f3f4f6 | #334155 |
--accent-foreground | 强调文字 | #1a1a1a | #f1f5f9 |
--muted | 弱化背景 | #f8f9fa | #1e293b |
--muted-foreground | 弱化文字 | #6b7280 | #94a3b8 |
--destructive | 危险 / 错误 | #ef4444 | #f87171 |
--destructive-foreground | 危险文字 | #ffffff | #0f172a |
--success | 成功 | #22c55e | #4ade80 |
--success-foreground | 成功文字 | #ffffff | #0f172a |
--warning | 警告 | #f59e0b | #fbbf24 |
--warning-foreground | 警告文字 | #ffffff | #0f172a |
--info | 信息 | #3b82f6 | #60a5fa |
--info-foreground | 信息文字 | #ffffff | #0f172a |
--border | 通用边框 | #e5e7eb | #334155 |
--input | 表单边框 | #e5e7eb | #334155 |
--ring | focus ring | #3b82f6 | #60a5fa |
排版
| 变量 | 默认值 |
|---|---|
--font-sans | system-ui, -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Segoe UI', Roboto, Arial, sans-serif |
--font-mono | 'SF Mono', 'Cascadia Code', Consolas, monospace |
圆角与阴影
| 变量 | 默认值 | 说明 |
|---|---|---|
--radius | 0.5rem | 圆角基准,业务最常改的就这个 |
--shadow-sm | 0 1px 3px rgb(0 0 0 / 8%) | 小阴影 |
--shadow-md | 0 4px 12px rgb(0 0 0 / 12%) | 中阴影 |
--shadow-lg | 0 10px 30px rgb(0 0 0 / 16%) | 大阴影 |
--scrim | linear-gradient(transparent, rgb(0 0 0 / 68%)) | 封面底部遮罩(信息层保文字可读,媒体站标配;恒深色不随主题变。background: var(--scrim) 一行消费、免 no-hardcode disable。Hero 大图复合艺术遮罩不在此列——品牌面业务自定) |
--scrim-top | linear-gradient(rgb(0 0 0 / 45%), transparent) | 封面顶部遮罩(播放器顶栏 / 页头压字场景) |
表单控件
| 变量 | 默认值 | 说明 |
|---|---|---|
--control-height | 2rem | 表单控件统一高度(SwInput / SwSelect / SwSegmented 默认等高;覆盖即全站控件同步缩放) |
--control-font-size | 0.875rem | 表单控件统一字号(SwInput 在触摸设备默认抬回 1rem,防 iOS 对 <16px 输入框聚焦自动放大) |
布局
| 变量 | 默认值 | 说明 |
|---|---|---|
--nav-height | 60px(移动端 56px) | 顶栏高度 |
--container-padding | 24px(移动端 16px;≥1280 48px、≥1600 96px) | 容器左右内边距 —— 宽屏留白由它提供,不是 --content-max-width |
--content-max-width | 1920px | 内容最大宽度(内容站基线,fw#283。1920 屏上等于满铺、2560 屏上居中留白;窄容器站在 overrides 写回 1200px) |
不收录 spacing(
--space-4等)、fontSize / fontWeight / lineHeight、色阶(--gray-50~--gray-900):Tailwind 启用时用p-4/text-lg,原生场景直接写数值更自由,framework 不做万能 Token 仓库。此外有少量组件内部 Token(如 SwImage 的
--sw-image-placeholder-bg),仅供对应组件定制,不属于通用调色板。
组件皮肤变量(modal / toast)
SwModal / SwToast 的默认皮肤由 framework 注入(modal.css / toast.css),皮肤变量默认值全部映射上方语义 Token——改语义 Token 全站联动(含弹窗 / toast),改下列变量只动对应组件。在 overrides.css 平级 :root 覆盖即可(弹窗 / toast 都 Teleport 到 body,scoped 样式覆盖不到)。整套皮肤可分别用 framework.styles.modal / styles.toast 设 false 关闭。
--sw-modal-*(SwModal / SwModalHost)
| 变量 | 默认值 | 说明 |
|---|---|---|
--sw-modal-overlay-bg | rgb(0 0 0 / 45%) | 遮罩底色(固定值,跨主题一致) |
--sw-modal-bg | var(--background) | 弹窗底色 |
--sw-modal-border | var(--border) | 弹窗描边 |
--sw-modal-shadow | var(--shadow-lg) | 弹窗阴影 |
--sw-modal-radius | 0.75rem | 弹窗圆角 |
--sw-modal-width | 28rem(内联兜底) | 弹窗宽度(实际 min(90vw, …)) |
--sw-modal-title-color | var(--foreground) | 标题色 |
--sw-modal-desc-color | var(--muted-foreground) | 描述色 |
--sw-modal-text-color | var(--foreground) | 正文色 |
--sw-modal-close-color | var(--muted-foreground) | 关闭 ✕ 色 |
--sw-modal-close-bg | transparent | ✕ 底色(设实色 + radius: 50% 即圆形带底) |
--sw-modal-close-radius | 6px | ✕ 圆角(设 50% 变圆形) |
--sw-modal-close-size | 1.75rem | ✕ 见方尺寸 |
--sw-modal-close-hover-bg | var(--muted) | ✕ hover 底色 |
--sw-modal-close-hover-color | var(--foreground) | ✕ hover 色 |
--sw-modal-footer-border | var(--border) | 底部操作栏分隔线 |
--sw-modal-btn-cancel-bg | var(--background) | 取消键底色 |
--sw-modal-btn-cancel-border | var(--border) | 取消键描边 |
--sw-modal-btn-cancel-color | var(--foreground) | 取消键文字 |
--sw-modal-btn-cancel-hover-bg | var(--muted) | 取消键 hover 底色 |
--sw-modal-btn-confirm-bg | var(--primary) | 确认键底色(hover 走 filter: brightness) |
--sw-modal-btn-confirm-color | var(--primary-foreground) | 确认键文字 |
--sw-toast-*(SwToast / useToast)
| 变量 | 默认值 | 说明 |
|---|---|---|
--sw-toast-bg | color-mix(in srgb, var(--background) 92%, transparent) | toast 底色(半透明毛玻璃) |
--sw-toast-color | var(--foreground) | 文字色 |
--sw-toast-border | var(--border) | 描边 |
--sw-toast-shadow | var(--shadow-md) | 阴影 |
--sw-toast-close-color | var(--muted-foreground) | 关闭 ✕ 色 |
--sw-toast-close-hover-bg | var(--muted) | ✕ hover 底色 |
--sw-toast-close-hover-color | var(--foreground) | ✕ hover 色 |
--sw-toast-icon-success | var(--success) | ✓ 图标色 |
--sw-toast-icon-error | var(--destructive) | ✕ 图标色 |
--sw-toast-icon-warning | var(--warning) | ! 图标色 |
--sw-toast-icon-info | var(--info) | i 图标色 |
原生 CSS 与 Tailwind 同源
tailwind-preset.css 用 Tailwind v4 的 @theme inline 把 :root 的每个 Token remap 到 Tailwind 命名空间——不定义新值,只做映射。于是一套 Token 同时驱动两种写法:
/* 原生 CSS */
.cta {
background: var(--primary);
color: var(--primary-foreground);
border-radius: var(--radius);
}
<!-- Tailwind 工具类(等价) -->
<button class="bg-primary text-primary-foreground rounded-lg">下单</button>
启用 Tailwind(sw config set features.tailwind true,见 CLI)后,业务侧 tailwind.css:
@import 'tailwindcss';
@import '@swift-web/framework/styles/tailwind-preset.css';
暗色切换时两种写法都自动跟随,无需 dark: 前缀——因为底层 var() 已被 theme.css 覆盖。
映射表
framework Token(原生 var()) | Tailwind 工具类 |
|---|---|
--background / --foreground | bg-background / text-foreground |
--card / --card-foreground | bg-card / text-card-foreground |
--popover / --popover-foreground | bg-popover / text-popover-foreground |
--primary / --primary-foreground | bg-primary / text-primary-foreground / border-primary |
--secondary / --accent / --muted(含各自 foreground) | bg-secondary / bg-accent / bg-muted …(同模式) |
--destructive / --success / --warning / --info | bg-destructive / text-success …(同模式) |
--border / --input / --ring | border-border / border-input / ring-ring |
--radius(派生 sm/md/lg/xl) | rounded-sm / rounded-md / rounded-lg / rounded-xl |
--font-sans / --font-mono | font-sans / font-mono |
--shadow-sm / --shadow-md / --shadow-lg | shadow-sm / shadow-md / shadow-lg |
断点 768px / 1024px | sm: / md: 前缀 |
圆角注意:原生 CSS 只有单一
var(--radius);rounded-sm/md/lg/xl由 preset 基于--radius用calc()派生(覆盖--radius即整体缩放)。别在手写 CSS 里引
var(--color-primary):--color-*是 preset 给 Tailwind 用的桥接变量(= var(--primary)),手写 CSS 直接引它属用错层——一律用语义 Tokenvar(--primary)。
改默认值
业务只在 app/assets/styles/overrides.css 的 :root 覆盖,原生与 Tailwind 两边自动跟随:
/* app/assets/styles/overrides.css */
:root {
--primary: #6366f1;
--radius: 0.75rem;
}
/* 暗色单独覆盖(theme.mode 为 system / toggle 时) */
:root[data-theme='dark'] {
--primary: #818cf8;
}
业务自定义 Token 用 --brand-* / --page-* 等前缀避开 framework 命名空间,集中定义到项目级 :root(同样不写 fallback)。详见 主题系统 · 自定义主题变量。
响应式工具类
responsive.css 提供断点约定 + 一组 .sw- 前缀工具类(前缀避免与 Tailwind .container 等冲突)。
断点(Mobile-first):
| 断点 | 范围 |
|---|---|
| Mobile | < 768px |
| Tablet | 768px – 1023px |
| Desktop | ≥ 1024px |
最小支持宽度 360px(Android 主流窄屏,framework#446):framework 组件的手机 e2e 统一按 360 跑。低于 360(iPhone SE / mini 开「放大显示」后的 320、折叠屏外屏)不保证、不设门禁。
工具类:
| 类名 | 作用 |
|---|---|
.sw-container | 响应式容器:max-width: var(--content-max-width) + 居中 + 左右 var(--container-padding) |
.sw-show-mobile | 仅 < 768px 显示(其余隐藏) |
.sw-hide-mobile | 在 < 768px 隐藏 |
.sw-hide-tablet | 在 768px – 1023px 隐藏 |
.sw-hide-desktop | 在 ≥ 1024px 隐藏 |
移动端(< 768px)下 --nav-height 自动降到 56px、--container-padding 降到 16px;宽屏反向递增(≥1280 → 48px,≥1600 → 96px,fw#284)。
🔴 两个布局变量分工不同,这是最容易用错的地方:
--content-max-width定「最宽到哪封顶」(只对 2K/4K 屏起作用——默认 1920 在 1920 屏上够不着、容器等于满铺);--container-padding定「任何宽度下边上留多少」,你在宽屏看到的留白全部来自它。想收窄内容区必须两个一起调:只改--content-max-width会在宽屏被 96px 的 padding 二次挤窄(如设 1280 后实际只剩 1088)。
responsive.css 有意不含「给 a / button / [role=button] 加全局 min-height」这类规则(framework#265 移除):规范要求的是热区而非视觉盒(WCAG 对 target 的定义是「可接受指针操作的区域」,Material Design 3 明确说热区「extending beyond the visual bounds of an element」),合规基线是 WCAG 2.5.8 AA 的 24×24(44×44 出自 AAA 的 2.5.5),且 inline 文本链接两条 SC 都明文豁免。视觉尺寸由组件自身设计决定;要放大触摸目标用下面的 sw-hit-area 扩热区。
紧凑控件热区扩展(opt-in):sw-hit-area —— 视觉刻意小于 44px 的按钮 / 链接(圆形关闭 ✕、chips 内移除钮、图标钮等)加 sw-hit-area class:由伪元素把点击热区扩到 44×44,视觉完全不变(业界推荐写法:position: relative + 伪元素 + pointer: coarse 门控)。framework 内置的紧凑图标钮已在组件内部挂好(SwThemeSwitch / SwPagination / SwAdPause 关闭钮 / SwEditorToolbar 工具钮 / 弹窗关闭钮 / SwSelect chips ✕ / 轮播圆点等),applier 用 Sw* 组件无需任何触摸端补丁;自建的紧凑控件按需自己加。注意:① 宿主元素不得再占用 ::before;② 密集网格不要加 —— 如 emoji 选择器(8 列 / gap 2px)扩到 44×44 会覆盖邻格造成误选。
<!-- 2rem 圆形图标钮:视觉保持正圆,触摸热区 44×44 -->
<SwButton class="close-btn sw-hit-area" aria-label="关闭">…</SwButton>
FAQ
为什么用 --primary 而不是 --color-primary?
遵循 Shadcn v4 约定,原生写 var(--primary) 比 var(--color-primary) 短、语义同样清楚。Tailwind 的 @theme inline 会把它 remap 成 Tailwind 命名空间的 --color-primary,对业务透明。
固定浅色(theme.mode: 'light')时这些 Token 还能用吗?
能。tokens.css 永远注入,只是没有暗色覆盖——业务是纯亮色主题。
业务想加自己的 Token 怎么办?
在 overrides.css / custom.css 的 :root 加 --brand-* 等自定义前缀变量即可。Tailwind 场景若要生成工具类,再在业务 tailwind.css 里 @theme { --color-brand: ...; }。
为什么不收录 spacing / fontSize Token?
Tailwind 启用时自带(p-4 / text-lg),原生场景业务写数值更自由。framework 不做万能 Token 仓库,避免给下游造成负担。