網站導覽

開始使用

語法教學

讀懂並組合 Master CSS class,加入互動狀態與條件,建立隨可用空間調整的指南卡片。

本頁目錄

指南卡片需要間距、易讀文字,以及能回應互動的連結。Master CSS 讓你在 HTML 中做這些決策:先選擇樣式,再加上選擇器或條件,決定樣式何時套用。

請先完成安裝。這些範例使用預設集,沒有自訂 utility 定義。將標記放在整合工具會掃描的來源檔中,並載入應用程式樣式表:

app.css
@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。

文字說明
  1. 樣式. fg-blue-60 將 color 設為 var(--color-blue-60)。
  2. 選擇器. :hover 在滑鼠移入時符合持有 class 的元素。
  3. 條件. @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。

文字說明
  1. p(12) 提供內距。:hover 檢查帶有 class 的元素,@sm 將規則限制於具名媒體條件,結尾 ! 將其宣告標為重要。
  2. 獨立的 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-mdpadding: var(--spacing-md)
gap-mdgap: var(--spacing-md)
fg-text-mutedcolor: 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)  }}
將滑鼠移到按鈕上,再用 Tab 移到按鈕。滑鼠移入會改變表面;鍵盤焦點會加上外框。此按鈕僅示範樣式,不會儲存資料。

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)    }  }}
選擇 Above sm 並將滑鼠移到連結上,連結會變藍。Below sm 時,滑鼠移入仍保留基礎文字顏色。內距也會在 sm 增加。這些控制項只調整預覽檢視區,與外層頁面無關。

@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    }  }}
切換 Narrow 與 Wide。具名容器控制卡片欄數與內距。將滑鼠移到 Read the guide,或用 Tab 取得焦點以檢查狀態;啟用連結會開啟「樣式重用」。

選擇變體時,在來源中保留完整 class 名稱,例如 'p-md' 與 'p-lg'。以 'p-' + size 這類片段組合 class,來源掃描器無法取得完整名稱。連續值請使用原生自訂屬性,搭配 (width:var(--progress)) 這類固定 class。掃描 class 名稱說明來源探索;渲染模式說明 CSS 何時產生。

預期樣式未出現時,先開啟產生的 CSS 檢查 class,再檢查選擇器指向哪個元素、條件是否相符、解析後的自訂屬性,以及瀏覽器中的競爭規則。重新排列 HTML class 無法解決階層串接問題。

接著閱讀主題 token,定義共用值;或閱讀樣式重用,選擇元件 class、utility 與 mixin。完整契約仍以 Reference 為準。