# Bootstrap 5 如何客製化自己的樣式
### 原始碼
先來看看 Bootstrap 5 的 scss 原始碼怎麼組成的,才能方便的進行客製化,所謂知己知彼,百戰不殆。
#### [Bootstrap 5 Repository](https://github.com/twbs/bootstrap/tree/main/scss)

#### [Bootstrap 4 Repository](https://github.com/twbs/bootstrap/tree/v4-dev/scss)

跟 Bootstrap 4 一樣可以依照自己想要用的功能引入相對的 scss,如同匡起來的部分。但眼尖的朋友應該發現 Bootstrap 5 多出了 bootstrap-utilities.scss 吧,這就是這次的重點,等等會講到。
#### 1. bootstrap-grid.scss
如果只想要用到 Bootstrap 5 格線系統,在自己的專案裡引入這個就可以了。
#### 2. bootstrap-reboot.scss
這支是 Bootstrap 裏面只有載入 reboot.scss 用來初始化 css 樣式,以便統一且一致性的建立樣式。是基於 Normalize.css 上進行規劃的,有需要的話,也可以單獨引入專案中。
#### 3. bootstrap.scss
所有的元件載入,包含格線系統、按鈕、手風琴、表單等等。什麼都要的話,直接引入到專案中是最方便的了,缺點就是很大一包。
#### 4. bootstrap-utilities.scss
這將會是重點中的重點,Bootstrap 5 利用了 scss 的 map-merge 定義了很多通用類別,而且強化了他的功能,讓我們可以單獨只引入這包來進行使用,與 Tailwind 不同的地方在於,他幾乎不用到 Javasript 就可以工具化許多 css 樣式。並且我們可以透過修改或覆蓋 utilities 來針對工具的客製化跟調整,是很讚讚的改變!!
### 建立客製化 scss 檔案
透過觀察 bootstrap.scss,我們知道說其實只要載入必須載入的 SCSS 檔案,其餘的只要用到再載入就好,這樣做的好處是可以減少打包後的大小,客製化自己的需求。([官網](https://getbootstrap.com/docs/5.0/customize/sass/#importing)也是這樣建議)
專案大概會長這樣(請依據需求調整)
```
project/
┣ node_modules/
┣ public/
┣ scss/
┃ ┗ customize.scss
┣ index.html
┣ package-lock.json
┗ package.json
```
建立 customize.scss 管理所有 scss 元件的入口,包含 Bootstrap 5 元件、變數覆蓋、utilities 甚至我們額外寫的 scss 檔。
:::warning
此處的作法用 npm 安裝 Bootstrap 5 後引入專案中。SCSS 編譯這邊就不多說了,自行用 gulp 或是 webpack 來實現。
:::
### 必須引入的 scss 檔案
```css=
// customize.scss
// 引入 bootstrap 預設的 variables、functions、 mixins
// 我們同時可以運用裡面的 function、mixins 來精簡我們的 scss 內容,color, svg, media-breakpoint-up 等
@import "../node_modules/bootstrap/scss/functions";
@import "../node_modules/bootstrap/scss/variables";
@import "../node_modules/bootstrap/scss/mixins";
```
在客製化 bootstrap 前,這三支檔案是必須引入的,需要載入他本身設定好的 functions、mixins、variables,以便元件的運用跟計算。
引入順序很重要,**function 一定要在 variables 前面**,因為 variables 裡面有用到 function 的方法。
#### 剩餘的元件或格線系統依照需求引入:
```css=
// Require
@import "../node_modules/bootstrap/scss/functions";
@import "../node_modules/bootstrap/scss/variables";
@import "../node_modules/bootstrap/scss/mixins";
// Optional
// Layout & components
@import "../node_modules/bootstrap/scss/root";
@import "../node_modules/bootstrap/scss/reboot";
@import "../node_modules/bootstrap/scss/type";
@import "../node_modules/bootstrap/scss/images";
@import "../node_modules/bootstrap/scss/containers";
@import "../node_modules/bootstrap/scss/grid";
@import "../node_modules/bootstrap/scss/forms";
@import "../node_modules/bootstrap/scss/badge";
@import "../node_modules/bootstrap/scss/buttons";
@import "../node_modules/bootstrap/scss/transitions";
@import "../node_modules/bootstrap/scss/nav";
@import "../node_modules/bootstrap/scss/navbar";
// ...etc
```
> root 這支建議載入,他會定義原生的 css 變數在瀏覽器的全域環境。
### SCSS 變數新增修改
基本上,我們可以透過覆蓋或是直接修改它 `_variables.scss` 中的變數來實現客製化的需求。
#### default 是什麼?
在修改變數前,先來談談 default 這個在 scss 關鍵字是起到什麼作用,簡單來說,他就是預設值。
範例:
```css=
$var : null;
$var : #777 !default;
p {
color : $var; /* color: #777; */
}
```
當 $var 是 null 或是沒有定義時,他會是預設值。
```css=
$var : #888;
$var : #777 !default;
p {
color : $var; /* color: #888; */
}
```
但是若 $var 是已經有值了,那預設值就不會指定。
----
#### 看一下 bootstrap 5 的 variables.scss

發現他裡面的變數幾乎都是預設值,所以我們可以輕易的從外面進行修改。
#### 建立 variable.scss 來覆蓋裡面的變數
##### 專案結構
```
project/
┣ node_modules/
┃ ┣ @popperjs/
┃ ┣ bootstrap/
┃ ┗ .package-lock.json
┣ public/
┣ scss/
┃ ┣ variable.scss // 自定義的 variable.scss
┃ ┗ customize.scss
┣ index.html
┣ package-lock.json
┗ package.json
```
```css=
// customize.scss
@import "./variables"; // 自定義的變數 scss 檔
$primary: red; // 若是修改的變數很少,也可以直接宣告
// Require
@import "../node_modules/bootstrap/scss/functions";
@import "../node_modules/bootstrap/scss/variables";
@import "../node_modules/bootstrap/scss/mixins";
// Optional...
```
官方文件有說,只能在載入 bootstrap 的 variables.scss 前作修改,不然部分樣式會變更無效。為什麼?
查看 bootstrap 的 variables.scss 可以發現,

他把變數一路指定下來,到 theme-colors-map 中,所以程式碼一行行執行時,還沒跑到我們自定義的變數時,他的主題顏色就被先指定了。
因此官方才建議自定義變數放到 bootstrap 的 variables.scss 前,當然如果後面也自定義 theme-colors-map 來修改內容,就可以把變數修改放後面了。
* 若你是整包載下來,也可以直接修改 bootstrap 的 variables.scss 變數內容。
### utilites 介紹
關鍵來了,Bootstrap 5 新增了這個通用類別工具,運用方式[官網有詳細說明](https://getbootstrap.com/docs/5.0/utilities/api/),我們這邊就來看看他是怎麼實現的,以及我們該如何修改或新增自己的工具。([六角](https://bootstrap5.hexschool.com/docs/5.0/utilities/api/))
#### scss map 變數管理
Bootstrap 5 巧妙的運用 scss 的 map、each 以及 if 把所有的 utilites 工具宣告出來。
#### utilities
```css=
$utilities: map-merge(
"display": (
responsive: true,
print: true,
property: display,
class: d,
values: inline inline-block block grid table table-row table-cell flex inline-flex none
),
"rounded": (
property: border-radius,
class: rounded,
values: (
null: $border-radius,
0: 0,
1: $border-radius-sm,
2: $border-radius,
3: $border-radius-lg,
circle: 50%,
pill: $border-radius-pill
)
)
)
```
利用了 map-merge 統一了所有 map,並在 map 中也可以再定義 map,形成一個類似於 JSON 格式的變數,差別在於大括號換成小括號而已。
utility map 包含了通用類群組的鍵值清單,這些清單接受以下選項:
| 選項 | 類型 | 介紹 |
| -------- | -------- | -------- |
| property | 必要 | 屬性的名稱,可以是字串或是字串陣列 ( 例如,水平的 padding 或 margin )。 |
| values | 必要 | 清單中的值或 map,在您不希望 class 名稱與值相同時使用。如果將 null 作為 map 鍵值,則不會編譯它。 |
| class | 可選 | 當您不希望 class 名稱與屬性相同時所使用的變數。如果您不提供 class 鍵值,且 property 鍵值是字串陣列,則 class 名稱將是 property 陣列的第一個元素。 |
| state | 可選 | 為通用類別生成的虛擬類別類清單,像是 :hover 或 :focus。沒有預設值。 |
| responsive | 可選 | 用於指示是否要生成響應式類別的布林值。預設值為 false。 |
| rfs | 可選 | 用於啟用流體縮放的布林值。可以查看 RFS 頁面以了解如何運作。預設值為 false。 |
| print | 可選 | 用於指示是否要生成 print 類別的布林值。預設值為 false。 |
| rtl | 可選 | 用於指示是否要將通用類別保留於 RTL 中的布林值。預設值為 true。 |
* 參考:[六角 Bootstrap 5 API 文件](https://bootstrap5.hexschool.com/docs/5.0/utilities/api/)
#### utilities/api.scss
$utilities 定義完成後,必須引入 utilities/api.scss,這支 scss 會幫我們把所有的 $utilities 裡定義的 map,轉換成 scss。 主要是運用 @each 跑過所有的 map,
再運用 @if 判斷 print 或 responsive 設定 $infix,最後呼叫定義好的 generate-utility 這支 mixin 來產生對應的 css。(詳細內容請參閱[原始碼](https://github.com/twbs/bootstrap/blob/main/scss/utilities/_api.scss)。)
```css=
// customize.scss
@import "./variables"; // 自定義的變數 scss 檔
// Require
@import "../node_modules/bootstrap/scss/functions";
@import "../node_modules/bootstrap/scss/variables";
@import "../node_modules/bootstrap/scss/mixins";
// Optional...
// ...載入自己需求的 scss 組件
// utilites
@import "../node_modules/bootstrap/scss/utilities";
// utilites/api
@import "../node_modules/bootstrap/scss/utilities/api";
```
### utilities api 說明
以下都可以在[官網](https://getbootstrap.com/docs/5.0/utilities/api/)或[六角](https://bootstrap5.hexschool.com/docs/5.0/utilities/api/)找到,就列出比較常用的來做範例。
#### 1. 簡單運用
```css
$utilities: (
"height": (
property: height,
values: (
25: 25%,
50: 50%,
75: 75%,
100: 100%,
auto: auto
)
),
);
```
輸出:
```css
.height-25 { height: 25%; }
.height-50 { height: 50%; }
.height-75 { height: 75%; }
.height-100 { height: 100%; }
.height-auto { height: auto; }
```
#### 2. 加入 class 生成對應的前綴
```css
$utilities: (
"height": (
property: height,
class: h, // class 對應前綴
values: (
25: 25%,
50: 50%,
75: 75%,
100: 100%,
auto: auto
)
),
);
```
輸出:
```css
.h-25 { height: 25%; }
.h-50 { height: 50%; }
.h-75 { height: 75%; }
.h-100 { height: 100%; }
.h-auto { height: auto; }
```
#### 3. 加入 responsive 生成對應斷點的 css 樣式
```css
$utilities: (
"height": (
property: height,
class: h, // class 對應前綴
responsive: true,
values: (
25: 25%,
50: 50%,
75: 75%,
100: 100%,
auto: auto
)
),
);
```
輸出:
```css
.h-25 { height: 25%; }
.h-50 { height: 50%; }
.h-75 { height: 75%; }
.h-100 { height: 100%; }
.h-auto { height: auto; }
@media (min-width: 576px) {
.h-sm-25 { height: 25%; }
.h-sm-50 { height: 50%; }
.h-sm-75 { height: 75%; }
.h-sm-100 { height: 100%; }
.h-sm-auto { height: auto; }
}
@media (min-width: 768px) {
.h-md-25 { height: 25%; }
.h-md-50 { height: 50%; }
.h-md-75 { height: 75%; }
.h-md-100 { height: 100%; }
.h-md-auto { height: auto; }
}
@media (min-width: 992px) {
.h-lg-25 { height: 25%; }
.h-lg-50 { height: 50%; }
.h-lg-75 { height: 75%; }
.h-lg-100 { height: 100%; }
.h-lg-auto { height: auto; }
}
@media (min-width: 1200px) {
.h-xl-25 { height: 25%; }
.h-xl-50 { height: 50%; }
.h-xl-75 { height: 75%; }
.h-xl-100 { height: 100%; }
.h-xl-auto { height: auto; }
}
@media (min-width: 1400px) {
.h-xxl-25 { height: 25%; }
.h-xxl-50 { height: 50%; }
.h-xxl-75 { height: 75%; }
.h-xxl-100 { height: 100%; }
.h-xxl-auto { height: auto; }
}
```
#### 4. 加入 state 生成對應狀態的 css 樣式
```css=
$utilities: (
"height": (
property: height,
class: h,
state: hover,
values: (
25: 25%,
50: 50%,
75: 75%,
100: 100%,
auto: auto
)
),
);
```
輸出:
```css=
.h-25-hover:hover { height: 25%; }
.h-50-hover:hover { height: 50%; }
.h-75-hover:hover { height: 75%; }
.h-100-hover:hover { height: 100%; }
.h-auto-hover:hover { height: auto; }
```
### utilites 新增修改
先來看看 Bootstrap 5 做了件很貼心的動作:

他把 utilites 變數,定義為預設值,這就代表說,我可以在外面直接用 utilites 變數宣告客製化的通用類別了,但是如果我在外面自己定義後,他這邊的通用類別不就會失效了嗎?
來看看他怎麼解決的:

> 1. 有想過為什麼他要設定兩個 utilites 變數嗎?
> 2. 為什麼第二個 utilites 最底下會有一個 utilites?
>
> 第一個 utilites 他其實是預設值,可以給我們外面覆蓋使用的。第二個就不是預設值了,他是用來 merge 第一個 utilites 和 Bootstrap 本身定義好的通用類別。
1. 在 Bootstrap 本身的 utilities 後面
自定義的 utilities 最好是放在 Bootstrap 本身的 utilities 後面,這樣才能覆蓋住修改過的通用類別。
```css=
// customize.scss
@import "./variables"; // 自定義的變數 scss 檔
// Require
@import "../node_modules/bootstrap/scss/functions";
@import "../node_modules/bootstrap/scss/variables";
@import "../node_modules/bootstrap/scss/mixins";
// Optional...
// ...載入自己需求的 scss 組件
// bootstrap 的 utilites
@import "../node_modules/bootstrap/scss/utilities";
// 自定義的 utilites
$utilities: map-merge(
$utilities, // bootstrap utilites 一定要引入,不然會沒辦法使用原本的通用類別
(
"object-fit": (
property: object-fit,
class: o-f,
responsive: true,
values: contain cover grab,
),
"BalooTamma2": (
property: font-family,
class: ff,
values: (
bt2: "Baloo Tamma 2"
),
),
"margin2": ( // 因為有 margin 所以用 margin2
responsive: true,
property: margin,
class: m,
values: (
100: 100px, // 新增 margin: 100px
auto: auto // 覆蓋或修改 margin: auto
)
),
)
);
// utilites/api 一定要在最後引入,會用到 mixins 跟 variables 定義的內容
@import "../node_modules/bootstrap/scss/utilities/api";
```
* map 變數宣告跟 bootstrap 的必須不一樣,但是產出的類別可以一樣,他會在 @each 都跑過一遍,依照 css 權重,相同層級設計,後面會覆蓋前面的。
* 自定義的 $utilities map 一定要在第一個引入 bootstrap 預設的 $utilities,這樣後面定義相同的通用類別時才可以覆蓋。
2. 在 Bootstrap 本身的 utilities 前面
那如果要放在前面也可以,自定義的 $utilities map 就可以不用在第一個放入 bootstrap 的 $utilities,因為你放在前面後,再前面根本沒有宣告過,會報錯。
在 bootstrap utilities.scss 中,他會把你前面定義的 $utilities 放在最後,來覆蓋他本來的樣式。
```css=
// customize.scss
@import "./variables"; // 自定義的變數 scss 檔
// Require
@import "../node_modules/bootstrap/scss/functions";
@import "../node_modules/bootstrap/scss/variables";
@import "../node_modules/bootstrap/scss/mixins";
// Optional...
// ...載入自己需求的 scss 組件
// 自定義的 utilites
$utilities: map-merge(
(
"object-fit": (
property: object-fit,
class: o-f,
responsive: true,
values: contain cover grab,
),
"BalooTamma2": (
property: font-family,
class: ff,
values: (
bt2: "Baloo Tamma 2"
),
),
"margin2": ( // 因為有 margin 所以用 margin2
responsive: true,
property: margin,
class: m,
values: (
100: 100px, // 新增 margin: 100px
auto: auto // 覆蓋或修改 margin: auto
)
),
)
);
// bootstrap 的 utilites
@import "../node_modules/bootstrap/scss/utilities";
// utilites/api 一定要在最後引入,會用到 mixins 跟 variables 定義的內容
@import "../node_modules/bootstrap/scss/utilities/api";
```
* 在那邊修改,所呈現的形式會不太一樣。
* **map 的名稱不要跟 bootstrap 的重複,會蓋掉本來的設定。** 至於為什麼會蓋掉,自行去看 scss 的官方文件囉。
### 結論
這些都是依照官網的建議做修正的,當然如果不怕改壞掉也可以直接載下來針對 utilities 和 variables 進行修改。variables 的客製化最好寫在 bootstrap 的 variables 前面。utilities 修改要注意是在 bootstrap 的 utilities 前還是後,會稍微不一樣。最簡單的方式就是在引入 bootstrap 前,把要客製化的 scss 先定義好囉。