Stitches
是已經進入穩定 1.0.0
版的 CSS-In-JS 套件,用起來手感相當成熟,常見的 CSS 規畫都能迎刃而解。
2022 年 6 月,Stitches 開發團隊被 WorkOS 收購;2023 年 2 月,已正式宣告停止維護,提到 React 18 讓 Stitches 的機制更棘手。
使用環境
"@stitches/react": ^1.2.8,
"react": ^17.0.1,
"next": ^12.1.4
這篇文章不會把 Stitches
文件裡提到的內容再講一遍,因為已經非常清楚,只會補充特色和小訣竅。
Stitches
利用 styled
來撰寫 CSS 屬性,跟已經廣泛使用的 styled-components
同個方式,我認為 Stitches
可以讓 CSS 樣式寫得更清楚,甚至補齊 CSS 先天沒有那麼方便的地方,Variants
功能可以說是 Stitches
之所以強大的核心。
接下來的內容
Stitches v.s. styled-components
在使用 Stitches
之前就是 styled-components
,我覺得差別在:
Stitches
- 無法傳入
props
值進入元件,需在元件指定已經做好的Variants
Variants
避免屬性覆蓋- 在龐大的專案,看起來還是很整齊
- 用
Variants
遠比沿用 (Extend) 樣式更清楚,在設計上就避免多層次沿用 - 用的人少
styled-components
- 可以傳入
props
值進入元件,讓樣式即時變化 - 無可避免會有很多被覆蓋的屬性
- 在龐大的專案,CSS 屬性或值裡塞滿條件與函式,看起來很亂
- 不斷因為沿用,得來回設定,多層次沿用會造成管理上的惡夢
- 用的人多
也可以從 State of CSS 參考,統計將近 9,000 位開發者的意見。
基本語法
const Sidebar = styled('ul', {
margin: 0,
padding: 0,
backgroundColor: 'hsl($shade300)'
});
先定義 HTML 標籤(或沿用 styled
元件),接著是 CSS 屬性物件,Variants 也要放在裡面。
const Sidebar = styled('ul', {
padding: 0,
backgroundColor: 'hsl($shade300)',
variants: {
responsive: {
mobile: {
margin: '$16',
},
tablet: {
margin: 0
}
}
}
});
// JSX
<Sidebar responsive={{ '@initial': 'mobile', 'bp768': 'tablet' }} />
Stitches
支援 TypeScript,不過目前參數並未限定型別:fontWeight: 500
跟 fontWeight: '500'
都可以用,會得到一樣的 CSS。
Theme Tokens
Theme Tokens
會以 CSS Custom Properties
呈現。Stitches
沒有 SCSS 自動編譯變數的功能,所以如果顏色有透明度的需求,建議使用 HSL 或 RGB,把 Token 設定為括弧裡的數字就好,例如:
theme: {
colors: {
shade1600: '162deg 2% 99%',
// ...
}
}
要用到透明度時就有彈性:
color: 'hsl($shade1600)',
backgroundColor: 'hsl($shade1600 / 0.25)'
多行與引號
在使用 CSS Grid 時,換行是有用途的,代表不同列。然而直接以單引號來換行:
grid: '"next" auto
"prev" auto
"home" auto / auto'
就會出現字串沒有關好的錯誤訊息:
Unterminated string literal
這情況要使用重音符 (grave accent):
grid: `"next" auto
"prev" auto
"home" auto / auto`
指定 <body>
的樣式
這段落只提 React 的做法。
Stitches 提供 global
樣式,是目前唯一能夠指定 <body>
樣式的方法,例如:頁面的背景顏色。
const pageBody = globalCss({
'body': {
backgroundColor: 'hsl($shade1600)'
}
});
接著就會發現:一旦切換到其他頁面,卻還保持打開頁面時的 <body>
樣式。因為這功能是設計給整個網站、每個頁面使用的,換頁也不會變,跟 styled-components
不同。
若有每頁換 <body>
樣式的需求,社群建議用 Attribute Selector 來區分。
const pageBody = globalCss({
'body[data-body-style=pie]': {
backgroundColor: 'hsl($pie0)'
}
});
接著以 useEffect
,在載入頁面時加上 <body>
的屬性
useEffect(() => {
document.body.setAttribute('data-body-style', 'pie');
}, []);
這樣子,換頁的時候就會更新 data-body-style
。
用來傳入數值的 Utils
文件裡其實也講得很清楚了,就像函式可以傳入 props。
經過測試,可以傳入 1 組或不傳入 props,無法傳入 2 組以上。
像這樣很常使用的一組 CSS:
utils: {
fullAbsolute: () => {
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0
}
}
沒有必要傳入 props 的時候,用法是:
'&::before': {
fullAbsolute: ''
}
以 TypeScript 使用 Utils 的限制
Stitches 升級成 1.0.0 之後,如果也有用 TypeScript,就會發現設定檔出現型別錯誤:
size: (value) => ({
width: value,
height: value
})
// Type '(value: any) => { width: any; height: any; }' is not assignable to type 'never'.ts(2322)
得要指定使用的 Token:
size: (value: Stitches.ScaleValue<'sizes'>) => ({
width: value,
height: value
})
也就是以後 size
這個 Util 能傳入的值,必須是在 sizes
Token 裡已經設定好的,無法像之前可以傳入任意值,如果專案還在東改西改的階段,就少了一些彈性。
混搭 styled
和 css
如果用了 css
const label = css({
color: hsl($shade1200),
transition: 'color 0.25s ease-out'
});
想要把這組樣式用在 styled
元件,可以這樣做:
const NameLabel = styled('span', label,
{
backgroundColor: 'hsl($shade100)'
}
);
結論
這裡記錄官方文件還沒有詳細說明的地方,但是寫 CSS 的體驗已經非常滿意,如果還有出現其他暫時解法或秘訣,會在這篇文章持續更新。