在設計系統和元件庫中使用自訂屬性的好處。
我是 Dave,是 Nordhealth 的前端資深開發人員。我負責設計和開發 Nord 設計系統,包括為元件程式庫建構 Web 元件。我想分享我們如何使用 CSS 自訂屬性解決 Web 元件樣式相關問題,以及在設計系統和元件程式庫中使用自訂屬性帶來的其他好處。
如何建構網頁元件
為了建構我們的 Web 元件,我們使用 Lit,這是一個提供許多樣板程式碼的程式庫,例如狀態、範圍樣式、範本等等。Lit 不僅輕巧,而且是建構於原生 JavaScript API,因此我們可以提供精簡的程式碼套件,充分利用瀏覽器現有的功能。
不過,Web 元件最吸引人的地方,是幾乎可與任何現有的 JavaScript 架構搭配使用,甚至完全不需架構。一旦網頁中參照了主要 JavaScript 套件,使用網頁元件就會非常類似使用原生 HTML 元素。唯一的明顯特徵是標記中一致的連字號,這是向瀏覽器表明這是網頁元件的標準。
Shadow DOM 樣式封裝
就像原生 HTML 元素有 Shadow DOM 一樣,Web 元件也有。Shadow DOM 是元素內部隱藏的節點樹狀結構。如要將這項功能視覺化,最佳做法是開啟網頁檢查器,然後開啟「Show Shadow DOM tree」選項。完成後,請嘗試在檢查器中查看原生輸入元素,您現在可以選擇開啟該輸入內容,並查看其中的所有元素。您甚至可以試著在其中一個網頁元件上進行這項操作,試著檢查自訂輸入元件的 Shadow DOM。
Shadow DOM 的優點 (或缺點,視您的觀點而定) 之一是樣式封裝。如果您在 Web 元件中編寫 CSS,這些樣式就不會外洩,並影響主頁面或其他元素;它們會完全包含在元件中。此外,為主頁面或父項 Web 元件編寫的 CSS 無法流入 Web 元件。
這種樣式封裝方式是元件程式庫的優點。這樣一來,無論父項網頁套用的樣式為何,使用者在使用我們的元件時,都能看到我們預期的樣貌,這點我們更有把握。為進一步確保,我們會將 all: unset;
新增至所有 Web 元件的根目錄或「主機」。
不過,如果使用您 Web 元件的使用者有正當理由變更特定樣式,該怎麼辦呢?也許有一段文字因內容而需要更強的對比度,或是邊框需要加粗?如果元件無法套用任何樣式,您該如何解鎖這些樣式選項?
這時,CSS 自訂屬性就能派上用場。
CSS 自訂屬性
自訂屬性的名稱非常貼切,您可以自行命名這些 CSS 屬性,並套用所需的任何值。唯一的規定是,您必須在這些字串前面加上兩個連字號。宣告自訂屬性後,您就可以使用 var()
函式在 CSS 中使用該值。
在繼承方面,所有自訂屬性都會沿用,並遵循一般 CSS 屬性和值的典型行為。任何套用至父項元素或元素本身的自訂屬性,都可以用於其他屬性的值。我們會透過 CSS 架構將自訂屬性套用至根元素,大量使用設計符記的自訂屬性,也就是說,無論是 Web 元件、CSS 輔助類別,還是開發人員想要從符記清單中擷取值,網頁上的所有元素都可以使用這些符記值。
透過使用 var()
函式,我們可以繼承自訂屬性,並穿透 Web 元件的 Shadow DOM,讓開發人員在設定元件樣式時,享有更精細的控制權。
Nord Web 元件中的自訂屬性
無論我們為設計系統開發哪個元件,都會針對 CSS 採取周全的做法,以便打造精簡且可輕鬆維護的程式碼。我們在根元素的 CSS 主架構中,將設計符記定義為自訂屬性。
這些符記值會在元件中參照。在某些情況下,我們會直接在 CSS 屬性上套用值,但在其他情況下,我們會定義新的內容相關自訂屬性,並將值套用至該屬性。
我們也會抽象化一些特定於元件但不在符記中的值,並將這些值轉換為內容相關的自訂屬性。與元件相關的內容自訂屬性可提供兩項重要優點。首先,這表示我們可以更「乾淨」地使用 CSS,因為該值可套用至元件中的多個屬性。
其次,它可讓元件狀態和變化版本的變更更加簡潔。舉例來說,當您為懸停或有效狀態 (或在本例中為變化版本) 設定樣式時,只需變更自訂屬性即可更新所有這些屬性。
不過,最強大的優點是,當我們在元件上定義這些內容相關的自訂屬性時,會為每個元件建立一種自訂 CSS API,供該元件的使用者使用。
上例顯示其中一個 Web 元件,其中的內容相關自訂屬性已透過選取器進行變更。這個整體做法所產生的結果,就是一個可為使用者提供足夠樣式彈性,同時仍可控管大部分實際樣式的元件。此外,我們身為元件開發人員,還可以攔截使用者套用的樣式。如果我們想調整或擴充其中一個屬性,使用者不必變更任何程式碼。
我們發現這種做法非常實用,不只對設計系統元件的開發人員有幫助,也能協助開發團隊在產品中使用這些元件。
進一步使用自訂屬性
在撰寫本文時,我們並未在說明文件中揭露這些內容相關的自訂屬性,但我們計畫將這些屬性公開,讓更多開發團隊能夠瞭解並善用這些屬性。我們的元件會透過資訊清單檔案在 npm 上打包,其中包含所有相關資訊。接著,我們會在部署說明文件網站時,將資訊清單檔案當作資料使用,這項作業會使用 Eleventy 及其全域資料功能完成。我們預計在這個資訊清單資料檔案中加入這些內容相關的自訂屬性。
我們希望改善的另一個領域,是這些內容相關自訂屬性如何繼承值。舉例來說,如果您想調整兩個分隔元件的顏色,目前必須使用選取器明確指定這兩個元件,或是直接在元素上套用樣式屬性中的自訂屬性。這似乎沒什麼問題,但如果開發人員可以在容納元素或根層級定義這些樣式,會更有幫助。
您必須直接在元件上設定自訂屬性值,是因為我們會透過元件主機選取器,在同一個元素上定義這些屬性。我們直接在元件中使用的全域設計符記會直接傳遞,不會受到這個問題的影響,甚至可以在父元素上攔截。如何兼具兩種系統的優點?
私人和公開自訂屬性
私人自訂屬性是 Lea Verou 所整合的內容,是元件本身的內容相關「私人」自訂屬性,但已設為「公開」自訂屬性,並設有備用項。
以這種方式定義內容相關的自訂屬性,表示我們仍可執行先前所有操作,例如繼承全域符記值,以及在元件程式碼中重複使用值;但元件也會順利繼承自身或任何父項元素的該屬性新定義。
雖然有人可能會認為這個方法並非真正「私密」,但我們仍認為這是解決我們擔心的問題的絕佳方法。我們會在適當時機在元件中解決這個問題,讓開發團隊能進一步控管元件用法,同時仍能享有我們已建立的防護機制。
希望這篇文章能讓您瞭解如何使用 CSS 自訂屬性搭配 Web 元件。歡迎在 Twitter 上與我分享你的想法,並告訴我你是否決定在自己的工作中使用其中任何一種方法。我的 Twitter 帳號是 @DavidDarnes。您也可以在 Twitter 上找到 Nordhealth 的 @NordhealthHQ,以及我的團隊成員:@Viljamis、@WickyNilliams 和 @eric_habich,他們都致力於整合這套設計系統,並實作本文提及的功能。
主頁橫幅圖片由 Dan Cristian Pădureț 提供