語法教學
讀懂並組合 Master CSS class,加入互動狀態與條件,建立隨可用空間調整的指南卡片。
本頁目錄
指南卡片需要間距、易讀文字,以及能回應互動的連結。Master CSS 讓你在 HTML 中做這些決策:先選擇樣式,再加上選擇器或條件,決定樣式何時套用。
請先完成安裝。這些範例使用預設集,沒有自訂 utility 定義。將標記放在整合工具會掃描的來源檔中,並載入應用程式樣式表:
@import '@master/css';每個產生的 CSS 展開區塊都呈現隨附 class 編譯後的輸出。開啟它即可查看送到瀏覽器的內容;不需把輸出複製進樣式表。
#讀懂 class
從一個連結開始:只有檢視區符合預設集 sm 查詢時,滑鼠移入才變成藍色。
<a href="/guide/reusing-styles" class="fg-blue-60:hover@sm"> Read the guide</a>此 class 有三個部分。fg-blue-60 選擇樣式,:hover 選擇狀態,@sm 加上條件。只有選擇器與條件都相符時才套用樣式。沒有這些後綴時,fg-blue-60 不受滑鼠移入或檢視區寬度限制。
讀懂樣式、選擇器與條件
只有檢視區符合 sm 時,顏色才套用到滑鼠移入的元素。兩個後綴都只屬於這個 class。
文字說明
- 樣式. fg-blue-60 將 color 設為 var(--color-blue-60)。
- 選擇器. :hover 在滑鼠移入時符合持有 class 的元素。
- 條件. @sm 將規則包在預設集具名 sm 媒體查詢內。選擇器與條件都相符時,此 class 才會套用。
產生的 CSS
@layer theme { :root, :host { --color-blue-60: oklch(51.83% .2687 266.1) }}@layer utilities { @media (width >= 52.125rem) { .fg-blue-60\:hover\@sm:hover { color: var(--color-blue-60) } }}產生的規則透過 CSS 變數設定 color,並放在媒體查詢內。選擇器會跳脫標點,以符合 HTML 中的字面 class 名稱。在 HTML 撰寫易讀形式即可;跳脫後的 CSS 選擇器屬於產生的輸出。
後綴屬於一個完整 class
括弧對應精確的字元範圍。空白結束第一個 class;它的狀態、條件與重要性不會延伸到 r-md。
文字說明
- p(12) 提供內距。:hover 檢查帶有 class 的元素,@sm 將規則限制於具名媒體條件,結尾 ! 將其宣告標為重要。
- 獨立的 r-md class 不含這些後綴。沒有 hover 或 sm 條件也能套用,且宣告不具重要性。
#選擇提供值的方式
常見決策使用預設集名稱。需要精確的區域值時,將數字傳給支援的函式,或寫出原生宣告:
| 寫法 | 決策 |
|---|---|
flex | 設定 display: flex 的固定 utility |
p-md | 具名間距的 token class |
p(12) | 直接值:12 像素內距 |
(max-width:32rem) | 包含完整 CSS 屬性與值的原生宣告 |
text-sm | 協調字級、行高與字距的配方 |
產生的 CSS
@layer theme { :root, :host { --font-size-sm: .875rem; --text-sm: var(--font-size-sm); --text-sm--line-height: 1.5; --text-sm--letter-spacing: 0; --spacing-md: 1rem }}@layer utilities { .text-sm { font-size: var(--text-sm); line-height: var(--text-sm--line-height, normal); letter-spacing: var(--text-sm--letter-spacing, normal) } .flex { display: flex } .p-md { padding: var(--spacing-md) } .p\(12\) { padding: calc(12 * 1px) } .\(max-width\:32rem\) { max-width: 32rem }}items-center、text-center 這類固定 utility 涵蓋常見宣告。m(4)、b(1) 與 grow(1) 這類直接值函式接受數字;長度函式使用像素,grow(1) 則提供無單位的 flex 係數。原生宣告在括號內保留完整屬性名稱。text-sm 或 grid-cols(2) 這類配方可協調多個宣告。所有形式都支援選擇器與條件後綴。
#在單一 class 中保留空格
HTML 以空白分隔 class。在引號字串之外,CSS 值需要空格時請寫成 |,逗號、斜線與括號則原樣保留:
<div class="(transform:translate(0,1rem)) (color:rgb(30|60|90/80%))"> <span class="(content:'New|collection')::before"></span></div>產生的 CSS
@layer utilities { .\(color\:rgb\(30\|60\|90\/80\%\)\) { color: rgb(30 60 90/80%) } .\(transform\:translate\(0\,1rem\)\) { transform: translate(0, 1rem) } .\(content\:\'New\|collection\'\)\:\:before::before { content: 'New|collection' }}顏色會變成 rgb(30 60 90 / 80%)。在引號包住的 content 字串內,直線符號保留字面意義:文字為 New|collection。Master CSS 也會正規化 calc(100%-2rem) 這類運算式的運算子間距,運算式則由瀏覽器計算。原生宣告邊界、引號值與跳脫方式請見宣告與值。
直線符號在引號內有不同意義
空白解碼只作用於引號字串之外。輸出字串仍是一個字面值。
文字說明
兩個字面輸入對應各自輸出。rgb(30|60|90) 變成 rgb(30 60 90);引號字串 New|collection 保留直線符號。
#共用值使用 token
Token class 將前綴連到具名值。p-md 與 gap-md 都讀取間距 token --spacing-md,但設定不同屬性:
| Class | 宣告 |
|---|---|
p-md | padding: var(--spacing-md) |
gap-md | gap: var(--spacing-md) |
fg-text-muted | color: var(--color-text-muted) |
產生的 CSS
@layer theme { :root, :host { --color-zinc-40: oklch(68.6% .0028 265); --color-zinc-60: oklch(48.6% .003 265) } :root, :host { --color-text-muted: light-dark(var(--color-zinc-60), var(--color-zinc-40)) } :root, :host { --spacing-md: 1rem }}@layer utilities { .fg-text-muted { color: var(--color-text-muted) } .gap-md { gap: var(--spacing-md) } .p-md { padding: var(--spacing-md) }}以下選用的間距值直接來自預設集:
--spacing-sm:.75rem(:root,:host)--spacing-md:1rem(:root,:host)--spacing-lg:1.5rem(:root,:host)
p-md 與 (padding:1rem) 看起來可能相同,但代表不同決策。調整 --spacing-md 會改變 token 內距,字面值則維持 1rem。樣式應跟隨共用值時使用 token。主題 token說明如何定義與限定自訂值的範圍;家族索引列出前綴與命名空間。
#組合獨立決策
讓每個決策使用自己的 class,建立小型表面:
<article class="p-md r-lg b(1) b-solid b-line-divider bg-surface-raised"> <h2 class="m(0) text-lg fg-text-strong">Field notes</h2> <p class="mt-sm mb(0) text-sm fg-text-muted">Keep useful ideas close.</p></article>產生的 CSS
@layer theme { :root, :host { --color-zinc-40: oklch(68.6% .0028 265); --color-zinc-60: oklch(48.6% .003 265); --color-zinc-90: oklch(24.6% .0022 265); --color-zinc-100: oklch(15% .0016 265) } :root, :host { --color-surface-raised: light-dark(var(--color-white), var(--color-zinc-90)); --color-line-divider: light-dark(oklch(0% 0 none / .12), oklch(100% 0 none / .12)); --color-text-strong: light-dark(var(--color-zinc-100), var(--color-white)); --color-text-muted: light-dark(var(--color-zinc-60), var(--color-zinc-40)) } :root, :host { --color-white: oklch(100% 0 none) } :root, :host { --font-size-sm: .875rem; --font-size-lg: 1.125rem; --text-sm: var(--font-size-sm); --text-sm--line-height: 1.5; --text-sm--letter-spacing: 0; --text-lg: var(--font-size-lg); --text-lg--line-height: 1.5; --text-lg--letter-spacing: 0; --radius-lg: .5rem; --spacing-sm: .75rem; --spacing-md: 1rem }}@layer utilities { .text-lg { font-size: var(--text-lg); line-height: var(--text-lg--line-height, normal); letter-spacing: var(--text-lg--letter-spacing, normal) } .text-sm { font-size: var(--text-sm); line-height: var(--text-sm--line-height, normal); letter-spacing: var(--text-sm--letter-spacing, normal) } .bg-surface-raised { background-color: var(--color-surface-raised) } .b-line-divider { border-color: var(--color-line-divider) } .r-lg { border-radius: var(--radius-lg) } .b-solid { border-style: solid } .b\(1\) { border-width: calc(1 * 1px) } .fg-text-muted { color: var(--color-text-muted) } .fg-text-strong { color: var(--color-text-strong) } .m\(0\) { margin: calc(0 * 1px) } .mt-sm { margin-top: var(--spacing-sm) } .mb\(0\) { margin-bottom: calc(0 * 1px) } .p-md { padding: var(--spacing-md) }}這裡 b(1) 設定邊框寬度,b-solid 設定樣式,b-line-divider 設定顏色。改變其中一項,不需重述其他項目。內距、圓角與文字樣式也保持獨立,讓之後的變化很小:較寬版面可改變內距,同時保留表面與文字。
#Class 順序不決定優先權
寫成 p-md p(12) 時,此屬性的直接值會勝過 token。反轉 HTML class 順序仍產生相同 CSS:
產生的 CSS
@layer theme { :root, :host { --spacing-md: 1rem }}@layer utilities { .p-md { padding: var(--spacing-md) } .p\(12\) { padding: calc(12 * 1px) }}除非刻意覆寫,基礎內距應擇一。混合原生元件 CSS 與 utility 時,串接層順序也有影響:預設集將一般 utilities 宣告放在 components 之後。串接層說明競爭規則;樣式重用說明如何保留元件共用外觀,同時改變單一實例。
#加入狀態與選擇器關係
將選擇器附加到行為改變的 class。這個按鈕在滑鼠移入時改變表面,透過鍵盤取得焦點時顯示外框:
表面變化與焦點外框
<button type="button" class="px-md py-xs text-sm r-md b(1) b-solid b-line-divider bg-surface-raised fg-text-strong shadow-sm bg-surface-inset:hover outline-offset(3) outline(2):focus-visible outline-solid:focus-visible">Save changes</button>產生的 CSS
@layer theme { :root, :host { --color-zinc-5: oklch(96.5% .0011 265); --color-zinc-90: oklch(24.6% .0022 265); --color-zinc-100: oklch(15% .0016 265) } :root, :host { --color-surface-inset: light-dark(var(--color-zinc-5), var(--color-zinc-100)); --color-surface-raised: light-dark(var(--color-white), var(--color-zinc-90)); --color-line-divider: light-dark(oklch(0% 0 none / .12), oklch(100% 0 none / .12)); --color-text-strong: light-dark(var(--color-zinc-100), var(--color-white)) } :root, :host { --color-white: oklch(100% 0 none) } :root, :host { --font-size-sm: .875rem; --text-sm: var(--font-size-sm); --text-sm--line-height: 1.5; --text-sm--letter-spacing: 0; --radius-md: .375rem; --spacing-xs: .5rem; --spacing-md: 1rem } :root, :host { --shadow-sm: 0 0 0 1px light-dark(oklch(0% 0 none / .04), oklch(100% 0 none / .05)), 0 1px 1px -.5px light-dark(oklch(0% 0 none / .045), oklch(0% 0 none / .3)), 0 2px 2px -1px light-dark(oklch(0% 0 none / .04), oklch(0% 0 none / .26)), 0 4px 4px -2px light-dark(oklch(0% 0 none / .04), oklch(0% 0 none / .24)) }}@layer utilities { .text-sm { font-size: var(--text-sm); line-height: var(--text-sm--line-height, normal); letter-spacing: var(--text-sm--letter-spacing, normal) } .bg-surface-raised { background-color: var(--color-surface-raised) } .b-line-divider { border-color: var(--color-line-divider) } .r-md { border-radius: var(--radius-md) } .b-solid { border-style: solid } .b\(1\) { border-width: calc(1 * 1px) } .shadow-sm { box-shadow: var(--shadow-sm) } .fg-text-strong { color: var(--color-text-strong) } .outline-offset\(3\) { outline-offset: calc(3 * 1px) } .py-xs { padding-block: var(--spacing-xs) } .px-md { padding-inline: var(--spacing-md) } .bg-surface-inset\:hover:hover { background-color: var(--color-surface-inset) } .outline-solid\:focus-visible:focus-visible { outline-style: solid } .outline\(2\)\:focus-visible:focus-visible { outline-width: calc(2 * 1px) }}Hover 後綴只屬於 bg-surface-inset:hover,不會讓相鄰 class 一起帶條件。這裡可使用原生偽類別與偽元素,包括 :disabled、:nth-child(2) 與 ::before。選擇器函式也適用:fg-blue-60:is(:hover,:focus-visible) 回應任一狀態。
選擇器也能指定其他元素。此例 class 放在 article 上,但 article 被滑鼠移入時,顏色會套用到其直接子連結:
<article class="fg-blue-60:hover>a"> <h3>Field notes</h3> <a href="/guide/reusing-styles">Read the guide</a></article>產生的 CSS
@layer theme { :root, :host { --color-blue-60: oklch(51.83% .2687 266.1) }}@layer utilities { .fg-blue-60\:hover\>a:hover > a { color: var(--color-blue-60) }}直接子元素使用 >,後代使用 _:fg-blue-60_a 會作用到後代連結。持有 class 的元素仍是後綴的定位基準。選擇器說明兄弟組合器、屬性、選擇器清單與權重。
#加入條件
條件決定 class 何時套用。加上 @sm,只在檢視區符合該具名查詢時改變樣式。此預覽讓開頭範例的行為可直接觀察:
滑鼠移入顏色取決於檢視區寬度
<div class="p-sm p-lg@sm"> <a href="/guide/responsive-design" target="_top" class="inline-flex py-xs text-sm fg-text-body underline fg-blue-60:hover@sm">Explore responsive design</a></div>產生的 CSS
@layer theme { :root, :host { --color-zinc-30: oklch(78% .0025 265); --color-zinc-70: oklch(39.8% .0028 265); --color-blue-60: oklch(51.83% .2687 266.1) } :root, :host { --color-text-body: light-dark(var(--color-zinc-70), var(--color-zinc-30)) } :root, :host { --font-size-sm: .875rem; --text-sm: var(--font-size-sm); --text-sm--line-height: 1.5; --text-sm--letter-spacing: 0; --spacing-xs: .5rem; --spacing-sm: .75rem; --spacing-lg: 1.5rem }}@layer utilities { .text-sm { font-size: var(--text-sm); line-height: var(--text-sm--line-height, normal); letter-spacing: var(--text-sm--letter-spacing, normal) } .fg-text-body { color: var(--color-text-body) } .inline-flex { display: inline-flex } .p-sm { padding: var(--spacing-sm) } .py-xs { padding-block: var(--spacing-xs) } .underline { text-decoration-line: underline } @media (width >= 52.125rem) { .p-lg\@sm { padding: var(--spacing-lg) } } @media (width >= 52.125rem) { .fg-blue-60\:hover\@sm:hover { color: var(--color-blue-60) } }}@sm 指向預設集的自訂媒體查詢。一次性的檢視區門檻可寫成 p-lg@media(width>=48rem) 這類原生查詢。其他條件形式也遵循相同模式:
| Class | 條件 |
|---|---|
hidden@media(print) | 列印時 |
grid@supports(display:grid) | 支援該宣告時 |
grid@sm@supports(display:grid) | 具名檢視區查詢與宣告支援同時成立 |
產生的 CSS
@layer theme { :root, :host { --spacing-lg: 1.5rem }}@layer utilities { @media print { .hidden\@media\(print\) { display: none } } @supports (display:grid) { .grid\@supports\(display\:grid\) { display: grid } } @media (width >= 52.125rem) { @supports (display:grid) { .grid\@sm\@supports\(display\:grid\) { display: grid } } } @media (width >= 48rem) { .p-lg\@media\(width\>\=48rem\) { padding: var(--spacing-lg) } }}多個條件由左至右巢狀包覆,因此每一層都必須相符。自訂媒體查詢與其他包覆形式請見條件。
兩個條件都必須相符
對 grid@sm@supports(display:grid) 而言,單獨符合任一條件都不會啟用宣告。
文字說明
二乘二矩陣只醒目標示檢視區符合 sm 且支援 display:grid 的交集。
#跟隨元件可用空間
即使檢視區寬度相同,側欄卡片也比主內容區卡片的空間小。在父元素加上 container-inline,再為卡片版面 class 加上容器條件。最後的範例以 (container-name:card) 為父元素命名,並用 @container(card|width>=28rem) 查詢。
父元素建立容器,子元素改變版面。28rem 門檻是明確的查詢值,與 --container-md 這類尺寸 token 分開。響應式設計透過具名與巢狀容器,進一步說明兩者差異。
#組合完整卡片
這張卡片結合前述決策。起初為單欄;具名父容器達到 28rem 時,連結移到文字旁,內距也隨之增加。連結的滑鼠移入與焦點樣式仍獨立於版面。
適應可用空間的指南卡片
<section class="container-inline (container-name:card)"> <article class="grid gap-lg p-md r-lg b(1) b-solid b-line-divider bg-surface-raised shadow-sm (grid-template-columns:1fr|auto)@container(card|width>=28rem) p-lg@container(card|width>=28rem)"> <div> <h3 class="m(0) text-lg font-regular fg-text-strong">Reusable styles</h3> <p class="mt-sm mb(0) text-sm fg-text-muted (max-width:38ch)">Keep shared appearance together and local choices in markup.</p> </div> <a href="/guide/reusing-styles" target="_top" class="inline-flex items-center justify-center self-start self-center@container(card|width>=28rem) gap-xs px-md py-xs text-sm (text-decoration-line:none) r-md b(1) b-solid b-line-divider bg-surface-raised fg-text-strong shadow-sm bg-surface-inset:hover outline-offset(3) outline(2):focus-visible outline-solid:focus-visible">Read the guide <span aria-hidden="true">→</span> </a> </article></section>產生的 CSS
@layer theme { :root, :host { --color-zinc-5: oklch(96.5% .0011 265); --color-zinc-40: oklch(68.6% .0028 265); --color-zinc-60: oklch(48.6% .003 265); --color-zinc-90: oklch(24.6% .0022 265); --color-zinc-100: oklch(15% .0016 265) } :root, :host { --color-surface-inset: light-dark(var(--color-zinc-5), var(--color-zinc-100)); --color-surface-raised: light-dark(var(--color-white), var(--color-zinc-90)); --color-line-divider: light-dark(oklch(0% 0 none / .12), oklch(100% 0 none / .12)); --color-text-strong: light-dark(var(--color-zinc-100), var(--color-white)); --color-text-muted: light-dark(var(--color-zinc-60), var(--color-zinc-40)) } :root, :host { --color-white: oklch(100% 0 none) } :root, :host { --font-weight-regular: 400; --font-size-sm: .875rem; --font-size-lg: 1.125rem; --text-sm: var(--font-size-sm); --text-sm--line-height: 1.5; --text-sm--letter-spacing: 0; --text-lg: var(--font-size-lg); --text-lg--line-height: 1.5; --text-lg--letter-spacing: 0; --radius-md: .375rem; --radius-lg: .5rem; --spacing-xs: .5rem; --spacing-sm: .75rem; --spacing-md: 1rem; --spacing-lg: 1.5rem } :root, :host { --shadow-sm: 0 0 0 1px light-dark(oklch(0% 0 none / .04), oklch(100% 0 none / .05)), 0 1px 1px -.5px light-dark(oklch(0% 0 none / .045), oklch(0% 0 none / .3)), 0 2px 2px -1px light-dark(oklch(0% 0 none / .04), oklch(0% 0 none / .26)), 0 4px 4px -2px light-dark(oklch(0% 0 none / .04), oklch(0% 0 none / .24)) }}@layer utilities { .text-lg { font-size: var(--text-lg); line-height: var(--text-lg--line-height, normal); letter-spacing: var(--text-lg--letter-spacing, normal) } .text-sm { font-size: var(--text-sm); line-height: var(--text-sm--line-height, normal); letter-spacing: var(--text-sm--letter-spacing, normal) } .items-center { align-items: center } .self-start { align-self: start } .bg-surface-raised { background-color: var(--color-surface-raised) } .b-line-divider { border-color: var(--color-line-divider) } .r-lg { border-radius: var(--radius-lg) } .r-md { border-radius: var(--radius-md) } .b-solid { border-style: solid } .b\(1\) { border-width: calc(1 * 1px) } .shadow-sm { box-shadow: var(--shadow-sm) } .fg-text-muted { color: var(--color-text-muted) } .fg-text-strong { color: var(--color-text-strong) } .container-inline { container-type: inline-size } .grid { display: grid } .inline-flex { display: inline-flex } .font-regular { font-weight: var(--font-weight-regular) } .gap-lg { gap: var(--spacing-lg) } .gap-xs { gap: var(--spacing-xs) } .justify-center { justify-content: center } .m\(0\) { margin: calc(0 * 1px) } .mt-sm { margin-top: var(--spacing-sm) } .mb\(0\) { margin-bottom: calc(0 * 1px) } .outline-offset\(3\) { outline-offset: calc(3 * 1px) } .p-md { padding: var(--spacing-md) } .py-xs { padding-block: var(--spacing-xs) } .px-md { padding-inline: var(--spacing-md) } .\(container-name\:card\) { container-name: card } .\(max-width\:38ch\) { max-width: 38ch } .\(text-decoration-line\:none\) { text-decoration-line: none } .bg-surface-inset\:hover:hover { background-color: var(--color-surface-inset) } .outline-solid\:focus-visible:focus-visible { outline-style: solid } .outline\(2\)\:focus-visible:focus-visible { outline-width: calc(2 * 1px) } @container card (width >= 28rem) { .self-center\@container\(card\|width\>\=28rem\) { align-self: center } } @container card (width >= 28rem) { .p-lg\@container\(card\|width\>\=28rem\) { padding: var(--spacing-lg) } } @container card (width >= 28rem) { .\(grid-template-columns\:1fr\|auto\)\@container\(card\|width\>\=28rem\) { grid-template-columns: 1fr auto } }}選擇變體時,在來源中保留完整 class 名稱,例如 'p-md' 與 'p-lg'。以 'p-' + size 這類片段組合 class,來源掃描器無法取得完整名稱。連續值請使用原生自訂屬性,搭配 (width:var(--progress)) 這類固定 class。掃描 class 名稱說明來源探索;渲染模式說明 CSS 何時產生。
預期樣式未出現時,先開啟產生的 CSS 檢查 class,再檢查選擇器指向哪個元素、條件是否相符、解析後的自訂屬性,以及瀏覽器中的競爭規則。重新排列 HTML class 無法解決階層串接問題。
接著閱讀主題 token,定義共用值;或閱讀樣式重用,選擇元件 class、utility 與 mixin。完整契約仍以 Reference 為準。







