CSS前端開發:has()CSS 選擇器無障礙2026

CSS :has():告別 JavaScript 的父選擇器

整理 CSS :has() 選擇器的進階應用:表單驗證、動態樣式、狀態管理,以及效能考量。2026 年所有主流瀏覽器全面支援。

· 5 分鐘閱讀

前端開發者想要「父選擇器」(parent selector)已經想了十多年。CSS 選擇器向來是「從父找子」而不是「從子找父」,所以當你需要「當某個子元素滿足條件時,父元素要變色」這類需求時,過去只能靠 JavaScript 幫你加上 class。:has() 選擇器改變了這件事。2026 年,所有主流瀏覽器都已經支援 :has(),是時候把它引進你的日常工作流程了。


前端工程師為什麼要關心 :has()

:has() 出現之前,CSS 的選擇器是嚴格單向的:你只能從父找子,不能從子找父。這個限制造成了很多不必要的 JavaScript 程式碼:

// 過去的常見做法:當表單驗證失敗時,用 JS 幫父容器加 class
const input = document.querySelector('input');
const formGroup = input.closest('.form-group');

input.addEventListener('invalid', () => {
  formGroup.classList.add('has-error');
});
/* 現在有了 :has(),純 CSS 就能做到同樣的事 */
.form-group:has(input:invalid) {
  border-color: red;
}

這個例子只是 :has() 的最基本應用。實際上 :has() 能做的事情遠比這個多,而且很多場景你過去壓根不會想到「這能用 CSS 做」。


:has() 的基本語法

語法結構

/* 選擇有 img 子元素的 figure */
figure:has(img) {
  margin-bottom: 1rem;
}

/* 選擇包含 .card 的 .grid */
.grid:has(.card) {
  gap: 1rem;
}

/* 選擇包含 :hover 狀態的 label */
label:has(input:checked) {
  font-weight: bold;
}

否定條件

:has() 也可以用 :not() 來表達否定條件:

/* 選擇「不包含」某個子元素的父元素 */
.article:has(p:last-child:not(.summary)) {
  /* 如果文章最後一個段落不是摘要,就加一些底部空間 */
  padding-bottom: 2rem;
}

實際應用場景

1. 表單驗證:input:invalid 時樣式父容器

這是 :has() 最受歡迎的應用之一。過去想要「當輸入框驗證失敗時,父容器變紅」,需要 JavaScript 監聽 invalid 事件並操作 DOM。

/* 當 input 處於 invalid 狀態時,父容器顯示錯誤樣式 */
.form-field:has(input:invalid:not(:placeholder-shown)) {
  border-color: #e53e3e;
}

.form-field:has(input:invalid:not(:placeholder-shown)) .error-message {
  display: block;
}

/* 當 input 驗證通過時,父容器顯示成功樣式 */
.form-field:has(input:valid:not(:placeholder-shown)) {
  border-color: #38a169;
}
<div class="form-field">
  <label for="email">Email</label>
  <input
    type="email"
    id="email"
    placeholder="輸入 email"
    required />
  <span class="error-message">請輸入有效的 email 地址</span>
</div>

2. 卡片沒有圖片時,調整標題樣式

/* 當 card 沒有 .card__image 時,調整標題的視覺位置 */
.card:not(:has(.card__image)) .card__title {
  font-size: 1.25rem;
  padding-top: 0;
}

/* 當 card 有圖片時,增加標題與圖片的間距 */
.card:has(.card__image) .card__title {
  margin-top: 1rem;
}

3. 下拉選單的自定義樣式

<select> 元素長期以來都很難樣式化,:has() 讓你可以在選項變化時,調整外層容器的樣式:

/* 根據 select 的值,調整容器的視覺效果 */
.select-wrapper:has(select[value="premium"]) {
  border: 2px solid gold;
  background: linear-gradient(to bottom, #fff8e1, #ffffff);
}

4. 無障礙:當連結打開新分頁時,自動加圖標

/* 當外部連結(target="_blank")時,在連結文字後面加上一個 SVG 圖標 */
a:has(> img)[target="_blank"]::after,
a[target="_blank"]:not(:has(> img))::after {
  content: '';
  display: inline-block;
  width: 12px;
  height: 12px;
  margin-left: 4px;
  background: url('/icons/external-link.svg') no-repeat center;
  background-size: contain;
}

5. 表單提交按鈕的禁用狀態

/* 當表單有任意 :invalid 的 input 時,按鈕禁用 */
form:has(input:invalid) .submit-btn {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* 當所有 input 都 valid 時,按鈕變為綠色 */
form:has(input:valid) .submit-btn {
  background-color: #38a169;
  color: white;
}

6. 響應式布局:當容器包含某個元素時調整 Grid

/* 當 .sidebar 存在時,調整主內容區域的 grid */
.layout:has(.sidebar) {
  grid-template-columns: 250px 1fr;
  gap: 2rem;
}

/* 當沒有 .sidebar 時,全寬顯示 */
.layout:not(:has(.sidebar)) {
  grid-template-columns: 1fr;
}

:has() 的效能考量

為什麼 :has() 可能影響效能

:has() 是 CSS 選擇器中少見的「反向查找」選擇器。瀏覽器在評估 :has() 時,需要「從子元素往父元素」檢查,而不是從父往子的單向遍歷。

/* 這個選擇器,瀏覽器需要:
   1. 找到每個 .card
   2. 檢查每個 .card 是否包含 .featured
   3. 對滿足條件的 .card 應用樣式 */
.card:has(.featured) {
  border-color: gold;
}

這個檢查在少數元素時不是問題,但當你的頁面有數百個 .card 時,累積的選擇器評估時間可能明顯影響渲染效能。

實務建議

  1. 避免過度巢狀的 :has()
/* ❌ 過度複雜的選擇器 */
.article:has(.content:has(img:has(.caption))) {
  /* 效能可能受影響 */
}

/* ✅ 相對簡單的選擇器 */
.article:has(img) {
  /* 效能好很多 */
}
  1. 避免在 :has() 裡使用昂貴的選擇器
/* ❌ 這類選擇器需要瀏覽器對每個 :hover 狀態都做複雜計算 */
body:has(.widget:hover) {
  /* 可能造成 scroll 時的效能問題 */
}

/* ✅ 用 class 或 data attribute 來標記狀態 */
body.has-widget-modal-open {
  /* 效能好很多 */
}
  1. 用 DevTools 測量實際渲染效能

Chrome DevTools 的 Performance 面板可以幫你測量 :has() 是否造成渲染瓶頸。如果選擇 :has() 之前頁面 FPS 是 60,加上後變成 55,你可能需要優化或用其他方式替代。


Progressive Enhancement:瀏覽器支援

2026 年,:has() 在所有主流瀏覽器都已經完全支援:

瀏覽器支援版本
Chrome105+
Edge105+
Safari15.4+
Firefox121+

如果你需要支援很舊的瀏覽器,可以用 @supports 做 Progressive Enhancement:

/* 對支援 :has() 的瀏覽器應用這些樣式 */
@supports selector(:has(*)) {
  .form-field:has(input:invalid) {
    border-color: red;
  }
}

/* 舊瀏覽器用 JS fallback */

與 JavaScript 比較:什麼時候用 CSS,什麼時候用 JS

:has() 能做到很多過去需要 JavaScript 的事情,但不代表所有場景都應該用 CSS:

適合用 :has()適合用 JavaScript
純展示樣式的變化需要操作多個 DOM 節點的複雜邏輯
表單驗證反饋需要操作非樣式屬性(attribute、data)
響應式布局調整需要與後端資料互動
無障礙相關的樣式需要儲存使用者偏好

結語

:has() 是 CSS 選擇器歷史上少有的「填補了基本功能缺口」的特性。它解決的不是邊緣問題,而是很多人每天都在用 JavaScript 處理的「當子元素滿足條件時,樣式化父元素」這個基本需求。

2026 年所有主流瀏覽器都支援了。還沒有用上的,可以開始在你的表單驗證、反饋樣式這些場景裡,試著用 :has() 取代那個曾經必要的 classList.add()


*## 延伸閱讀

本文是「2026 CSS 實用技術」系列文章之一。*