📋 目錄
前端開發者想要「父選擇器」(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 時,累積的選擇器評估時間可能明顯影響渲染效能。
實務建議
- 避免過度巢狀的
:has()
/* ❌ 過度複雜的選擇器 */
.article:has(.content:has(img:has(.caption))) {
/* 效能可能受影響 */
}
/* ✅ 相對簡單的選擇器 */
.article:has(img) {
/* 效能好很多 */
}
- 避免在
:has()裡使用昂貴的選擇器
/* ❌ 這類選擇器需要瀏覽器對每個 :hover 狀態都做複雜計算 */
body:has(.widget:hover) {
/* 可能造成 scroll 時的效能問題 */
}
/* ✅ 用 class 或 data attribute 來標記狀態 */
body.has-widget-modal-open {
/* 效能好很多 */
}
- 用 DevTools 測量實際渲染效能
Chrome DevTools 的 Performance 面板可以幫你測量 :has() 是否造成渲染瓶頸。如果選擇 :has() 之前頁面 FPS 是 60,加上後變成 55,你可能需要優化或用其他方式替代。
Progressive Enhancement:瀏覽器支援
2026 年,:has() 在所有主流瀏覽器都已經完全支援:
| 瀏覽器 | 支援版本 |
|---|---|
| Chrome | 105+ |
| Edge | 105+ |
| Safari | 15.4+ |
| Firefox | 121+ |
如果你需要支援很舊的瀏覽器,可以用 @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()。
*## 延伸閱讀
- Interop 2026 CSS 新特性全景 — 2026 年 CSS 新功能瀏覽器支援狀態
- CSS 完整指南 — CSS Cascade 分層管理
本文是「2026 CSS 實用技術」系列文章之一。*