Skip to content

Atomic Order And Cascade

Atomic CSS 有一個很常見的陷阱:markup 裡 class tokens 的順序,並不會直接決定最後結果。

瀏覽器在處理衝突時,比的是 stylesheet 裡產生的 CSS declarations。當兩個 atomic declarations 的 specificity 相同時,較晚出現在 CSS 裡的 declaration 會獲勝。

常見的 atomic 排序問題

如果你使用過 UnoCSS 或 TailwindCSS 這類 utility-first 工作流,應該見過這個問題。

tsx
export function UtilityClassOrderDemo() {
	return (
		<>
			<div className="px-4 pl-2">left padding should end at 0.5rem</div>
			<div className="pl-2 px-4">left padding should end at 1rem</div>
		</>
	)
}

上面兩個元素,最後仍然可能指向同一批共享的全域 declarations:

css
/* 共享的 utility CSS 只會在整個專案中輸出一次 */

.px-4 {
	padding-left: 1rem;
	padding-right: 1rem;
}

.pl-2 {
	padding-left: 0.5rem;
}

也就是說,兩個元素最終吃到的是同一套 stylesheet 順序,不會因為 class attribute 裡 token 的順序不同而改變。

如果 .pl-2.px-4 之後輸出,兩個元素最後都會得到 padding-left: 0.5rem

如果 .px-4.pl-2 之後輸出,兩個元素最後都會得到 padding-left: 1rem

Markup 改了,cascade 卻沒跟著改。

為什麼會這樣

Atomic systems 會盡量在所有地方重用同一個 declaration。

這種重用對輸出大小很有幫助,但也帶來一個現實限制:一旦某個共享 declaration 已經存在於全域 stylesheet,後面的 component 就不能只靠 token 順序去改變它在 cascade 裡的作用方式。

當 declarations 在效果上彼此重疊時,這個問題就會浮現,例如:

  • paddingpadding-left 這種 shorthand 與 longhand 組合
  • background-colorbackground 這種 aggregate family
  • overflow-xoverflow 這種 patched shorthand family
  • all 與任何後續 property 組合的 universal reset

PikaCSS 有什麼不同

PikaCSS 依然會去重一般的 atomic declarations,但它把「效果重疊」當成需要正面處理的問題。

當 engine 發現同一個 selector scope 裡,後面的 declaration 可能改變前面 declaration 的實際結果時,它會把這個後續 declaration 標記成對順序敏感,而不是去重用全域快取 class。

換句話說,真正重要的地方,author 順序還是會被保留下來。

ts
// pika() 是全域函式,不需要另外匯入

const compactCard = pika({
	padding: '16px',
	paddingLeft: '8px',
})

const spaciousCard = pika({
	padding: '24px',
	paddingLeft: '8px',
})

export { compactCard, spaciousCard }
css
/* 產生的 CSS 大致會是這個樣子 */

.pk-a {
	padding: 16px;
}

.pk-b {
	padding-left: 8px;
}

.pk-c {
	padding: 24px;
}

.pk-d {
	padding-left: 8px;
}

在這個例子裡,padding-left: 8px 會刻意出現兩次。

第二個 padding-left 不會重用第一個 component 的 class,因為一旦重用,它就會和前面緊鄰的 padding: 24px 脫鉤。PikaCSS 會保留一個新的 atomic class,確保後續的重疊關係仍然按照正確的局部順序生效。

核心取捨

PikaCSS 不會為了解決 cascade 問題,就乾脆把 deduplication 全域關掉。

只有那些在效果上彼此重疊的後續 declarations 會變成對順序敏感。無關的 declarations 仍然會在整個專案中重用同一個 atomic class。

這讓 PikaCSS 取得一個更實用的平衡:

  • 對重疊 declarations 保持可預測的 cascade
  • 對無關 declarations 保持一般的 atomic reuse
  • 不需要人工推理全域 utility 的輸出順序

這對真實專案代表什麼

你可以照著最能表達意圖的順序撰寫 style definitions,並信任 engine 在 property effects 重疊時仍會保留這個意圖。

你還是得思考一般 CSS 規則,例如 specificity、selector 形狀與 layers。PikaCSS 不是要繞過 cascade,而是讓 atomic generation 和 cascade 好好配合,不要彼此打架。

什麼情況下最重要

這個行為在以下專案中尤其重要:

  • 混用 shorthand 與 longhand declarations 的 component variants
  • 會展開成重疊 CSS families 的 plugin-generated declarations
  • 使用 all 的 reset 模式
  • 期待後續 author intent 保持局部且可預測的團隊

Next