靜態營銷頁與文件站仍大量手寫 HTML。過去要依子元素狀態改變父階外觀,往往得加一層 JavaScript 或重複的包裝類名。:has() 關係偽類把方向反過來:區塊可在內部任一核取方塊被勾選時改變描邊,表單列可在輸入無效時醒目提示——常無需打包工具,甚至一行 JS 都不寫。2026 年,只要目標客群以 Safari 15.4 及以上 為主,許多團隊已能把 :has() 用於視覺提示;前提是你在真實 WebKit 上驗收,並控制選擇器複雜度。本文整理實用寫法、與 JS 及 容器查詢 的取捨表,以及如何把檢查納入 Safari 品質檢驗工作流程 並在租用的 Mac mini 上執行。
靜態頁上 :has() 解決什麼問題
傳統 CSS 只能沿祖先向下選後代(.theme-dark .card),無法表達「因為內部有某種子節點,所以父階要變樣」。產品團隊過去用 React 同步類名,或用 data-* 鏡像子狀態。對零 JS 的靜態站——法務聲明、API 小站、活動落地頁——這種摩擦會擋住細膩的互動回饋。:has() 把意圖寫清楚:「這張卡片處於錯誤態,因為它含有 .error-text。」無障礙方面,可見標籤與 ARIA 仍依規範提供;:has() 負責裝飾層,不替代語意。
因為規則是宣告式的,設計師在 CodePen 裡試好的選擇器可以直接進 Eleventy、Hugo 產物,無需水合。代價是心智負擔:關係選擇器很強,也容易巢狀過深。建議在團隊規範裡寫死「營銷模板裡 :has() 後不得超過兩個組合符」,方便後人用 grep 維護。
與元件庫混用時,注意不要把 :has() 綁到會頻繁增刪的 Portal 根節點;靜態頁雖少動態掛載,文件站裡的搜尋浮層仍可能改變 DOM 結構。把 :has() 根固定在卡片、欄位群組、表格段等穩定容器上,能減少意外全域失效。若你同時維護多語系分支,記得在每種語言的 DOM 結構下各測一遍,避免譯文長度變子選擇器匹配範圍漂移。
可直接複用的語法模式
從自包含元件入手。欄位群組在任一控制項取得焦點可見時發光:
.field-group:has(:focus-visible) {
box-shadow: 0 0 0 3px rgba(0, 113, 227, 0.35);
}
表格列在儲存格帶缺失翻譯標記時標示:
tr:has(td[data-missing="true"]) {
background: rgba(255, 59, 48, 0.08);
}
可與 :not() 組合做排除態;但要牢記純 CSS 的「空值」判斷能力弱於限制校驗 API。業務規則超出屬性選擇器表達力時,用少量指令碼切換類別,比硬寫十條 :has() 分支更可維護。
在深色主題下,:has() 與 color-scheme、prefers-reduced-motion 可並列使用:父階在子階含影片且使用者偏好減少動效時降低陰影強度。此類模式在靜態 HTML 裡同樣生效,只要媒體查詢與 :has() 的優先順序在樣式表中寫清楚,避免後載入的主題表意外覆蓋。
Safari 支援時間軸與測試要點
寫入相容矩陣時可抄錄以下事實:
- Safari 15.4(2022 年 3 月)在 macOS 12.3、iOS/iPadOS 15.4 上提供 :has(),與其它引擎 Chromium 105 檔位的支援節奏接近。
- 企業環境若仍凍結在 Monterey 12.3 以下,分析裡若仍有 3%~4% 營收來自這些組建,應為關鍵描邊提供不依賴 :has() 的配色。
- 部分 WebKit 修復會先出現在 Safari Technology Preview;若社群討論涉及 :has() 內
nth-child等行為,應在穩定版與 STP 間比對後,再寫入每次靜態發佈的回歸說明。
在 README 裡把最低 Safari 版本與 Node、套件管理器版本並列,避免外包在衝刺週「為求穩」整段刪掉 :has()。每半年依企業機隊換代情況複查這一行。
若網站透過 CDN 做灰度,可在灰度層對舊版 Safari 使用者注入簡化 CSS,而不是維護兩套完整 HTML。:has() 作為增強層時,先保證無 :has() 時仍可讀完正文與送出表單,再逐步打開裝飾效果。
決策表::has()、JS 還是 @container
| 需求 | 優先 | 原因 |
|---|---|---|
| 子無效時醒目父階 | :has(:invalid) | 零 JS,離線靜態 HTML 可用。 |
| 側欄寬度變化時改柵格軌 | @container | 寬度驅動版面屬於容器查詢,不是 :has()。 |
| 送出 JSON 並顯示伺服器端錯誤 | JavaScript | 網路與 ARIA 即時更新超出 CSS 範圍。 |
| 任一核取方塊勾選時顯示圖示 | :has(:checked) | 宣告式;搭配正確 label 仍可存取。 |
| 捲動深度節流打點 | JavaScript | CSS 無法安全送出信標。 |
當 :has() 與 @container 同時相關時,建議分工:容器管宏觀版面,:has() 管元件內微觀狀態。很少需要在同一元素上疊滿兩種機制。
與 容器查詢 聯用時,先畫清「誰回應父盒尺寸、誰回應子狀態」的示意圖,再落筆到 CSS,可減少 code review 時的來回。
效能與選擇器衛生
後代變化時,瀏覽器必須重新評估關係選擇器。在單頁長達 4000+ 節點時,常見的 body:has(.modal[open]) { overflow: hidden; } 通常仍可接受;但若把多條 :has() 綁在高頻動畫上就會吃力。WebKit 對局部子樹的樣式失效較為高效;請把 :has() 根保持在卡片與表單段附近。若效能錄製裡紫色「樣式重算」條在中階筆電的 hover 風暴中持續寬於 2~3ms,應重構為在包裝器上由單一委派監聽切換一個類別。
用 Stylelint 等限制選擇器特異度,可防止「選擇器湯」。在雲端 Mac 跑 CI,能在合併前暴露僅 macOS 才出現的回歸;租用 Apple Silicon 比為短期活動再買一台筆電更省 CapEx。
安全上 :has() 不能按任意文字內容匹配,只有結構與偽類,本身不是資料外洩通道;但仍應避免把含使用者產生類名的完整選擇器串打進第三方分析。
遷移舊版 BEM 修飾符如 .card--error 時,可安排兩個迭代並行:保留類名給統計鉤子,由 :has() 驅動視覺,待事件追蹤遷到 data 屬性後再刪修飾符。靜態站常在刪掉冗餘切換指令碼後 gzip 再省 2~4KB。
列印樣式中若使用 :has(),請在「列印預覽」裡單獨測 Safari:部分使用者只用列印 PDF 歸檔合約頁,忽略這一路徑可能留下白底上看不見的錯誤描邊。
雲端 Mac mini 上的驗收清單
租用帶 Safari 穩定版的 macOS,用 file:// 或本機靜態伺服器載入建置產物,跑三條路徑:純鍵盤表單、強制無效態、200% 縮放檢視裁切。缺陷橫掃時用 1280×720 錄影即可。團隊沒有實體 Mac 時,SSH 加偶發 VNC 與本站其它 Safari 文章所述一致。
Apple Silicon Mac mini 在並行 lint 與樣式分析時仍保持安靜,適合夜間批次跑二十套 HTML 模板。雲端存取讓跨境協作共用一台驗收機,而不必郵寄筆電。
加一條自動化冒煙:載入頁面,把範例輸入在合法/非法間切換,對 :has() 根節點截一張計算樣式面板圖。PNG 與發版標籤放一起幾乎不佔空間,卻能在 WebKit 與 Chromium 表現不一致時省下大約 25~35 分鐘 的爭論時間。
若與 STP 對比工作流程 結合,建議在變更日誌裡標註「本次是否在 STP 複測」,避免把預覽版獨有行為當成穩定版承諾給客戶。
常見問題
Safari 從哪一版支援 :has()?
Safari 15.4(2022 年 3 月,macOS 12.3 與 iOS 15.4)起支援。仍需 Safari 14 的團隊要提供不依賴 :has() 的兜底。
:has() 能完全取代表單裡的 JavaScript 嗎?
純視覺提示常常可以;驗證提示、ARIA live、與伺服器的往返仍需要 JS 或更完整的 HTML 語意。
:has() 會影響效能嗎?
超大頁面上的深度巢狀鏈可能增加樣式重算。保持局部、避免綁到數千節點頁面的每次 hover,並在 Web Inspector 裡實測。
有節制地使用 :has(),能在保持靜態 HTML 精簡的同時去掉樣板程式碼。務必搭配真實 Safari 工作階段——而不只是 Chromium——以捕捉 WebKit 特有的失效行為。Mac mini 在 Apple Silicon 上提供原生 WebKit、低待機功耗與安靜執行,適合長時間手工品質檢驗。MacHTML 提供可 SSH/VNC 的實體 Mac mini 租用,便於在發布視窗內搭建 WebKit 實驗環境,活動結束再縮容,避免硬體閒置。
需要 Safari 實機做 :has() 驗收?
租用 Apple Silicon Mac mini,在真實 WebKit 上跑 Web Inspector,本地繼續用慣用的編輯器寫靜態頁。