CSS :has() 選擇器進階實戰:取代 JavaScript 的父元素選取與條件樣式神器
CSS :has() 選擇器絕對是近年來最讓我興奮的 CSS 新功能,沒有之一。以前要根據子元素的狀態改變父元素樣式,只能靠 JavaScript 監聽事件再手動加 class。現在一行 CSS 就搞定了。我第一次用 :has() 重構舊專案的表單驗證樣式時,刪掉了整整 47 行 JavaScript,那個爽快感真的很難形容。
:has() 基礎回顧:CSS 終於有父選擇器了
:has() 最基本的功能就是「父選擇器」。它讓你可以根據元素的子元素或後續兄弟元素的狀態來選取該元素。語法很直覺:
/* 選取包含 img 的 .card */
.card:has(img) {
grid-template-rows: 200px 1fr;
}
/* 選取包含 :checked 的 label */
label:has(input:checked) {
background-color: var(--primary);
color: white;
}
到 2026 年,所有主流瀏覽器都已經完整支援 :has(),包括 Chrome、Firefox、Safari 和 Edge。所以你可以放心大膽地在生產環境使用了。
進階父元素選取技巧
巢狀 :has() 與複雜選取
:has() 可以巢狀使用,這打開了很多可能性。比如說你想選取「包含有圖片的 figure 元素的 article」:
article:has(figure:has(img)) {
/* 有圖片的文章用不同佈局 */
display: grid;
grid-template-columns: 1fr 2fr;
}
兄弟元素選取
:has() 不只能往下找子元素,還能配合 + 和 ~ 選取兄弟元素。這在做表單的時候超好用:
/* 當 input 後面有 .error-message 時,改變 input 樣式 */
input:has(+ .error-message) {
border-color: red;
box-shadow: 0 0 0 3px rgba(255, 0, 0, 0.1);
}
:has() 搭配 :not()、:is()、:where() 組合技
:has() 跟其他偽類選擇器搭配起來,威力更是倍增。這裡分享幾個我在實際專案中常用的組合:
/* 選取沒有圖片的 card(用 :not 反轉) */
.card:not(:has(img)) {
padding: 2rem;
}
/* 選取包含任何互動元素的容器(用 :is 簡化) */
.container:has(:is(button, a, input)) {
cursor: pointer;
}
/* 選取包含 h2 或 h3 的 section,但不影響權重(用 :where) */
section:has(:where(h2, h3)) {
border-left: 4px solid var(--accent);
}
如果你對 CSS 原生作用域控制也有興趣,推薦看看 CSS @scope 原生作用域教學,:has() 搭配 @scope 可以做到非常精確的樣式控制。
純 CSS 表單驗證樣式
這是我覺得 :has() 最實用的場景。以前要在使用者填錯表單時顯示紅色邊框和錯誤訊息,都得用 JavaScript。現在可以用純 CSS 做到八成以上的效果:
/* 表單群組:當 input 無效時改變整個群組樣式 */
.form-group:has(input:invalid:not(:placeholder-shown)) {
--field-color: var(--error-red);
}
.form-group:has(input:valid) {
--field-color: var(--success-green);
}
/* 送出按鈕:只有全部欄位都填寫才啟用 */
form:has(input:placeholder-shown) .submit-btn {
opacity: 0.5;
pointer-events: none;
}
form:not(:has(input:placeholder-shown)):not(:has(input:invalid)) .submit-btn {
opacity: 1;
pointer-events: auto;
}
那個 :placeholder-shown 的技巧是關鍵——它可以判斷 input 有沒有被使用者填過,避免頁面一載入就全部顯示紅色錯誤。
條件佈局:根據內容自動切換
:has() 讓你可以根據內容存在與否自動調整佈局,不需要手動加 class。這在做 CMS 的時候特別有用:
/* 有側邊欄時用兩欄佈局,沒有時用單欄 */
.page:has(.sidebar) {
display: grid;
grid-template-columns: 1fr 300px;
}
.page:not(:has(.sidebar)) {
max-width: 800px;
margin: 0 auto;
}
/* 根據卡片數量調整 grid */
.grid:has(.card:nth-child(4)) {
grid-template-columns: repeat(2, 1fr);
}
.grid:has(.card:nth-child(7)) {
grid-template-columns: repeat(3, 1fr);
}
搭配 CSS 原生 if() 條件邏輯一起使用,你幾乎可以用純 CSS 處理所有的條件樣式邏輯了。
互動元件實作
純 CSS 主題切換
/* 用隱藏的 checkbox 當狀態 */
body:has(#theme-toggle:checked) {
--bg: #1a1a2e;
--text: #eee;
--card-bg: #16213e;
color-scheme: dark;
}
body:not(:has(#theme-toggle:checked)) {
--bg: #ffffff;
--text: #333;
--card-bg: #f5f5f5;
color-scheme: light;
}
手風琴選單
.accordion-item:has(input:checked) .accordion-content {
max-height: 500px;
opacity: 1;
padding: 1rem;
}
.accordion-item:has(input:checked) .accordion-header {
background: var(--primary);
color: white;
}
漸進增強與 @supports
雖然 2026 年瀏覽器支援已經很好了,但如果你的專案需要支援非常舊的瀏覽器,還是建議用 @supports 做漸進增強:
/* 基本樣式,所有瀏覽器都能用 */
.card { padding: 1rem; }
/* 支援 :has() 的瀏覽器才套用 */
@supports selector(:has(*)) {
.card:has(img) {
padding: 0;
}
.card:has(img) .card-body {
padding: 1rem;
}
}
這個做法也可以搭配 CSS Popover API + Anchor Positioning 一起用,確保新功能不會在舊瀏覽器上壞掉。
效能注意事項
:has() 是目前少數需要「往上找」的 CSS 選擇器,理論上效能會比一般選擇器差。但實際測試下來,在現代瀏覽器裡影響微乎其微。不過有幾個建議:
- 避免在非常深的 DOM 結構裡用 :has() 搭配通用選擇器(如 :has(*))
- 盡量縮小 :has() 的搜尋範圍,用具體的選擇器而非模糊的
- 不要在大量元素上同時使用複雜的 :has() 組合
結語:CSS 越來越強了
:has() 選擇器的出現,代表 CSS 正在從「純粹的樣式語言」進化成「帶有邏輯能力的樣式語言」。以前需要 JavaScript 做的很多 UI 邏輯,現在純 CSS 就能搞定,而且效能更好、維護更簡單。如果你還沒開始用 :has(),現在就是最好的時機。從表單驗證開始,慢慢擴展到條件佈局,你會發現自己的 JavaScript 程式碼越寫越少,而 CSS 越寫越開心。
繼續閱讀
CSS Masonry 瀑布流原生佈局教學:不靠 JavaScript 也能做出 Pinterest 排版
CSS Masonry Layout 終於原生支援了!這篇教學帶你用純 CSS 實作瀑布流排版,不再需要 Masonry.js 或其他套件。
相關文章
你可能也喜歡
探索其他領域的精選好文