樣式與 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 倉庫,避免給下游造成負擔。