---
# System prepended metadata

title: 語意化樣式

---

# 語意化樣式

追求語意化、可擴充性的撰寫樣式。

## 從命名開始

在一個 HTML 元素上，我們將會替元素的 `id`、`class` 屬性加上各種名稱，分為以下幾種：

### 模組命名

> 名其所圖

模組要是功能的說明，應為單一，通常是抽象名詞，比如像是 `#user-register`、`#product-management` 等。

應避免混雜畫面的資訊在其中，如下：

- `#user-register-form`，應改為 `#user-register`，假設存在於該功能為名的路徑，可以考慮使用 `#main` 作為名稱。

### 元件命名

> 名其所見

元件要是個實體名詞，比如像是 `.button`、`.form` 等。

應避免混雜功能與特徵在其中，如下：

- `.user-register-form`，視情況應改為：`.form`、`.form.for-user-register` (適用存在特殊樣式、功能時)
- `.large-button`，應改為：`.large.button`

### 子元件命名

> 名其所屬

子元件大致與元件相同，唯一不同之處在於，子元件無法單獨存在與使用，必須歸屬於該元件之下，因此，需加上該元件的前綴，如 `.table-row`、`.header-bar` 等。

應避免不必要的子元件命名發生，如下：

- `.table-row-col`，應改為：`.table-col`

### 變體命名

> 名其所形

變體涵蓋了狀態與外觀的變化，通常是個形容詞抑或抽象名詞，比如像是 `.animated`、`.error` 等。
若含有其他實體名詞時，則可採用 `-` 後綴進行表達，如 `.menu-.icon`，藉此避免與[元件命名](#元件命名)可能發生的衝突。

應避免過於精簡的描述，如下：

- `.left`，視情況應改為： `.left-text-aligned` (指元件中的文字對齊靠左)、`.is-left-aligned` (子項靠左對齊)

### 響應式變體命名

> 名其所適

使用大小代號命名 `xxs`、`xs`、`sm`、`md`、`lg`、`xl`、`xxl`。

- `-on-{size}` 後綴：僅適用於該斷點
- `-on-{size}-to-{size}` 後綴：適用於該斷點以上，到特定斷點為止
- `-on-{size}-or-above` 後綴：適用於該斷點以上
- `-on-{size}-or-below` 後綴：適用於該斷點以下，不鼓勵使用

### 特殊命名

> 名其所殊

特殊命名不得獨立撰寫樣式，應搭配元件，作為額外的說明。

- `as-` 前綴：視為特定元件，主要用於供容器辨識。
  - 如：`.card > .card-body` 透過增加 `.card > .as-card-body`，讓 `.carousel.as-card-body` 能被視為 `.card-body` 而被排版。
- `for-` 前綴：描述用途或功能，可用於容器辨識或改變自身排版。如：`.form.for-register`
- `is-` 前綴：描述特徵，主要用於供容器辨識。
  - 如：`.form > .primary.button` 透過增加 `.form > .button.is-primary`，讓 `.secondary.button.is-primary` 能夠如同 `.primary.button` 被排版。
- `in-` 前綴：在特定容器中。主要用於改變自身排版，如：`.carousel.in-card`，在和[響應式變體命名](#響應式變體命名)組合使用時，會是：`.in-card-on-sm-or-below`

## 撰寫樣式

### 選擇器

> 各司其職

建議都使用子項選擇器 `>`，能夠有效的避免污染的發生。

例如：

```css
.cards .button /* 載入卡片按鈕 */ {
  margin-left: auto; /* 我想按鈕要靠右邊 */
}

.card .button /* 卡片內文按鈕 */ {
  margin-right: auto; /* 我想按鈕要靠左邊 */
}
```

對應畫面的結構通常會是這樣：

```xml
<div class="cards">
  <button class="button">more</button>
  <div class="card">
    <button class="button">detail</button>
  </div>
  ...
</div>
```

因此實際上，卡片內文按鈕會在中間，而不是在左邊。因為該也符合 `.cards .button`

理想的方式：

```css
.cards > .button /* 載入卡片按鈕 */ {
  margin-left: auto; /* 我想按鈕要靠右邊 */
}

.card > .button /* 卡片內文按鈕 */ {
  margin-right: auto; /* 我想按鈕要靠左邊 */
}
```

### 響應式

> 涇渭分明

排版方式不同時，建議使用嚴格斷點，能夠有效的避免污染的發生。

發現需要過多的覆蓋樣式時（如：`margin-top` 改回 `0px`，需要 `unset` 等），建議直接使用嚴格斷點進行區分。

```css
/* 在小螢幕要疊起來用滑動的 */
@media (max-width: 767.98px) {
  .cards.for-recommendation {
    position: relative;
  }

  .cards.for-recommendation > * {
    position: absolute;
  }

  .cards.for-recommendation > .cards-arrow {
    top: 50%;
    transform: translateY(-50%);
  }

  .cards.for-recommendation > .cards-arrow.is-left {
    left: 0;
  }

  .cards.for-recommendation > .cards-arrow.is-right {
    right: 0;
  }
}

/* 在中螢幕顯示一部分，另一部分透過按鈕來展開 */
@media (min-width: 768px) and (max-width: 1279.98px) {
  .cards.for-recommendation {
    height: var(--cards-height);
  }

  .expanded.cards.for-recommendation {
    height: var(--expanded-cards-height);
  }
}

/* 中螢幕、大螢幕都是彈性排版，不推薦簡寫成中螢幕以上 */
@media (min-width: 768px) and (max-width: 1279.98px), (min-width: 1280px) {
  .cards.for-recommendation {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }

  .cards.for-recommendation > .cards-arrow {
    display: none;
  }
}

/* 小螢幕、大螢幕都沒有展開按鈕 */
@media (max-width: 767.97px), (min-width: 1280px) {
  .cards.for-recommendation > .button.for-expanding {
    display: none;
  }
}
```

### 排版思路

> 適得其所

我們不應該假設任何的元件或子元件自身的位置，因此我們應該要避免在單純的元件上直接那麼撰寫樣式。

我們很容易寫出這樣的樣式：

```css
.button {
  margin-left: auto; /* 按鈕通常都置中吧 */
  margin-right: auto;
}
```

但實際上使用時：

```css
.form > .button.for-submit {
  margin-right: unset; /* 並沒有，按鈕要靠右邊，用起來真不直觀 */
}
```

理想的過程：

```css
.button {
  /* 按鈕不知道會擺哪，也不知道會多大 */
}
```

```css
.form > .button.for-submit {
  margin-left: auto; /* 表單中，供送出的按鈕會靠右邊 */
}
```

避免使用如 `top`、`left`、`bottom`、`right`、`margin`、`transform` 等會使位置在預設情況下不符預期的樣式。

追求元件可以在任何大小下有著一個理想的樣子，如被設定 `width`、`height`、`font-size` 時，能夠使內容等比例的縮放或位移。

### 分層處理

> 層層有別

我們通常會寫出這樣的樣式：

```css
.button {
  transition: transform 400ms;
}

.button:active {
  transform: scale(0.8); /* 按下的時候會有些反應 */
}
```

但實際上使用時：

```css
.form > .button.for-submit {
  transform: translateX(50%); /* 是沒錯，但是我要保持移動啊 */
}
```

通常會想的解決方法也許是：

```css
.form > .button.for-submit:active {
  transform: translateX(50%) scale(0.8); /* 把原本的樣式也加上位移就好啦 */
}
```

不過...

```css
.button:active {
  transform: scale(0.6); /* 啊，縮放 0.6 不錯 */
}
```

然後我們就很有可能忘記要改變 `.form` 裡面的部分

解決方式：

```xml
<form class="form">
  <div class="form-button-wrapper as-button for-submit">
    <button class="button"></button>
  </div>
</form>
```

```css
.form > .as-button.for-submit, /* 排版兼容類別 `.as-button` */
.form > .button.for-submit {
  transform: translateX(50%);
}

.form-button-wrapper.for-submit > .button {
  /* 原有的 button 大小對應等處理 */
}
```

得益於[排版思路](#排版思路)，對於 `.form` 來說，他只負責將 `.button` 或 `.as-button` 進行排版，不知道裡頭具體有什麼差別。

因此我們可以透過在 `.button` 外面包覆一層，將會混在一起的效果分離撰寫，如 `transform`、`transition`、`animation` 等。

### 變體應用

> 森羅萬象

透過父項與子項的變體表達，使排版能夠多元的實現。

```css
.form > .primary.button /* 預設情況下，主要按鈕靠右邊 */ {
  order: 1;
}

.form > .as-button.is-primary, /* 作為主要按鈕，更靠右邊 */
.form > .button.is-primary /* 為主要按鈕，更靠右邊 */ {
  order: 2;
}

.form > .as-button.is-primary, /* 作為特殊按鈕，再更靠右邊 */
.form > .button.for-special /* 為特殊按鈕，再更靠右邊 */ {
  order: 3;
}

.button-order-reversed.form > .primary.button /* 按鈕排序反向表單，主要按鈕靠左邊 */ {
  order: -1;
}

.button-order-reversed.form > .as-button.is-primary, /* 按鈕排序反向表單，作為主要按鈕，更靠左邊 */
.button-order-reversed.form > .button.is-primary /* 按鈕排序反向表單，為主要按鈕，更靠左邊 */ {
  order: -2;
}

.button-order-reversed.form > .as-button.is-primary, /* 按鈕排序反向表單，作為特殊按鈕，再更靠左邊 */
.button-order-reversed.form > .button.for-special /* 按鈕排序反向表單，為特殊按鈕，再更靠左邊 */ {
  order: -3;
}
```

雖說如此，但在構思元件的排版時，應盡可能確保其泛用性，避免不必要的樣式覆蓋不斷產生。

### 善用變數

> 去其糟粕

有些變體可能只是一小部分的變動，卻總是額外寫了很多的樣式程式碼。

可參考[變體應用](#變體應用)中的程式碼，採用變數達到一定簡化：

```css
.form {
  /* 1 靠右、-1 靠左、0 取消，預設為 1 */
  --button-in-form-order: 1;
}

.form > .primary.button {
  order: var(--button-in-form-order);
}

.form > .as-button.is-primary,
.form > .button.is-primary {
  order: calc(var(--button-in-form-order) * 2);
}

.form > .as-button.is-primary,
.form > .button.for-special {
  order: calc(var(--button-in-form-order) * 3);
}

.button-order-reversed.form {
  --button-in-form-order: -1;
}
```

比較之下可以發現，使用變數後，變體樣式的部分僅有 `.button-order-reversed.form`，而不需要以此再替 `.button, .as-button` 額外撰寫樣式。
