样式与 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精简 resetstyles.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.cssSwModal 默认皮肤(--sw-modal-* 映射语义 Token)styles.modal !== false(默认开)
toast.cssSwToast 默认皮肤(--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 是其上的文字色。

变量用途LightDark
--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
--ringfocus ring#3b82f6#60a5fa

排版

变量默认值
--font-sanssystem-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

圆角与阴影

变量默认值说明
--radius0.5rem圆角基准,业务最常改的就这个
--shadow-sm0 1px 3px rgb(0 0 0 / 8%)小阴影
--shadow-md0 4px 12px rgb(0 0 0 / 12%)中阴影
--shadow-lg0 10px 30px rgb(0 0 0 / 16%)大阴影
--scrimlinear-gradient(transparent, rgb(0 0 0 / 68%))封面底部遮罩(信息层保文字可读,媒体站标配;恒深色不随主题变。background: var(--scrim) 一行消费、免 no-hardcode disable。Hero 大图复合艺术遮罩不在此列——品牌面业务自定)
--scrim-toplinear-gradient(rgb(0 0 0 / 45%), transparent)封面顶部遮罩(播放器顶栏 / 页头压字场景)

表单控件

变量默认值说明
--control-height2rem表单控件统一高度(SwInput / SwSelect / SwSegmented 默认等高;覆盖即全站控件同步缩放)
--control-font-size0.875rem表单控件统一字号(SwInput 在触摸设备默认抬回 1rem,防 iOS 对 <16px 输入框聚焦自动放大)

布局

变量默认值说明
--nav-height60px(移动端 56px)顶栏高度
--container-padding24px(移动端 16px;≥1280 48px、≥1600 96px)容器左右内边距 —— 宽屏留白由它提供,不是 --content-max-width
--content-max-width1920px内容最大宽度(内容站基线,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-bgrgb(0 0 0 / 45%)遮罩底色(固定值,跨主题一致)
--sw-modal-bgvar(--background)弹窗底色
--sw-modal-bordervar(--border)弹窗描边
--sw-modal-shadowvar(--shadow-lg)弹窗阴影
--sw-modal-radius0.75rem弹窗圆角
--sw-modal-width28rem(内联兜底)弹窗宽度(实际 min(90vw, …))
--sw-modal-title-colorvar(--foreground)标题色
--sw-modal-desc-colorvar(--muted-foreground)描述色
--sw-modal-text-colorvar(--foreground)正文色
--sw-modal-close-colorvar(--muted-foreground)关闭 ✕ 色
--sw-modal-close-bgtransparent✕ 底色(设实色 + radius: 50% 即圆形带底)
--sw-modal-close-radius6px✕ 圆角(设 50% 变圆形)
--sw-modal-close-size1.75rem✕ 见方尺寸
--sw-modal-close-hover-bgvar(--muted)✕ hover 底色
--sw-modal-close-hover-colorvar(--foreground)✕ hover 色
--sw-modal-footer-bordervar(--border)底部操作栏分隔线
--sw-modal-btn-cancel-bgvar(--background)取消键底色
--sw-modal-btn-cancel-bordervar(--border)取消键描边
--sw-modal-btn-cancel-colorvar(--foreground)取消键文字
--sw-modal-btn-cancel-hover-bgvar(--muted)取消键 hover 底色
--sw-modal-btn-confirm-bgvar(--primary)确认键底色(hover 走 filter: brightness)
--sw-modal-btn-confirm-colorvar(--primary-foreground)确认键文字

--sw-toast-*(SwToast / useToast)

变量默认值说明
--sw-toast-bgcolor-mix(in srgb, var(--background) 92%, transparent)toast 底色(半透明毛玻璃)
--sw-toast-colorvar(--foreground)文字色
--sw-toast-bordervar(--border)描边
--sw-toast-shadowvar(--shadow-md)阴影
--sw-toast-close-colorvar(--muted-foreground)关闭 ✕ 色
--sw-toast-close-hover-bgvar(--muted)✕ hover 底色
--sw-toast-close-hover-colorvar(--foreground)✕ hover 色
--sw-toast-icon-successvar(--success)✓ 图标色
--sw-toast-icon-errorvar(--destructive)✕ 图标色
--sw-toast-icon-warningvar(--warning)! 图标色
--sw-toast-icon-infovar(--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 / --foregroundbg-background / text-foreground
--card / --card-foregroundbg-card / text-card-foreground
--popover / --popover-foregroundbg-popover / text-popover-foreground
--primary / --primary-foregroundbg-primary / text-primary-foreground / border-primary
--secondary / --accent / --muted(含各自 foreground)bg-secondary / bg-accent / bg-muted …(同模式)
--destructive / --success / --warning / --infobg-destructive / text-success …(同模式)
--border / --input / --ringborder-border / border-input / ring-ring
--radius(派生 sm/md/lg/xl)rounded-sm / rounded-md / rounded-lg / rounded-xl
--font-sans / --font-monofont-sans / font-mono
--shadow-sm / --shadow-md / --shadow-lgshadow-sm / shadow-md / shadow-lg
断点 768px / 1024pxsm: / md: 前缀

圆角注意:原生 CSS 只有单一 var(--radius);rounded-sm/md/lg/xl 由 preset 基于 --radius 用 calc() 派生(覆盖 --radius 即整体缩放)。

别在手写 CSS 里引 var(--color-primary):--color-* 是 preset 给 Tailwind 用的桥接变量(= var(--primary)),手写 CSS 直接引它属用错层——一律用语义 Token var(--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
Tablet768px – 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 仓库,避免给下游造成负担。