iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0

上一篇做完 Interactivity API 我們手上其實已經累積了一整套資產:十幾個區塊、一份設計 Token、一套配色和字級,但它們散在 blocks/ 資料夾和 theme.json 裡,你看不到全貌也不知道品質齊不齊。

今天做兩件事順序很重要:**把整套設計系統攤在一頁上檢視,再拿它們組成完整頁面,**最後介紹 Everything-WP 的 /make-block,它把從「一個參考網站」到「一整套區塊加一頁 UI Library」的流程一次跑完。

為什麼是「先檢視後組裝」

直覺上會想先組首頁,畢竟那是看得到成果的東西,但實際做過幾次之後我改成相反的順序,理由很簡單:你不會拿著一盒還沒清點的零件直接開始拼

組頁面的動作是「挑元件、決定順序、排版」,前提是你知道手上有哪些元件、每個長什麼樣。更重要的是設計上的破綻要在組頁之前修掉,等頁面組好了才發現三張卡片的圓角不一致、間距節奏亂掉,你要回頭改的就不只是區塊,還有已經用了它們的每一頁。

UI Library 就是那張清單,也是那道驗收關卡。

UI Library 是什麼,為什麼要做

如果你用過 React 生態的 Storybook,概念一模一樣:把每個元件的每種變體(variant)都渲染出來排在一頁當作設計系統的活文件。設計師來對樣式、工程師來查有哪些現成元件、客戶來看整體風格都看這一頁。

對區塊佈景主題來說它還多一層意義,因為我們每個區塊都吃 theme.json 的屬性,把它們全排在一頁時你能立刻看出設計有沒有一致:所有卡片的圓角一樣嗎?間距的節奏對嗎?主色出現的地方協調嗎?分散在各頁時看不出來的破綻,集中在一頁就無所遁形。

用一個 pattern 承載它

我的做法是把 UI Library 做成一個 pattern,放在 patterns/ui-library.php。版面是固定的兩欄:左邊 25% 是一個 sticky 的錨點選單,把區塊分成 Shared、Elements、Sections 三組;右邊 75% 每個區塊一個段落,把它的每種變體都當成真實的 live 實例渲染出來。

左邊那個選單的樣式,就寫在 style.css 裡(這也是 style.css 在區塊佈景主題少數還會寫 CSS 的地方之一):

@media (min-width: 1200px) {
  .ui-lib-nav {
    position: fixed;
    top: var(--wp--preset--spacing--70);
    left: var(--wp--preset--spacing--60);
    width: 12rem;
    background-color: var(--wp--preset--color--base);
    border: 1px solid var(--wp--preset--color--border);
    border-radius: var(--wp--custom--radius--large);
  }
}

UI Library 要真的渲染不是截圖

UI Library 最容易做壞的地方是把它做成一堆靜態截圖,正確做法是讓每個變體都是「真的那個區塊」,直接把 wp:block-theme/service-card 放進去渲染。這樣它永遠反映區塊的當下樣子,改了區塊這頁自動更新,這也是為什麼要把它做成 pattern,而不是一張設計稿。

實務上我會再加一條規則:每完成一個區塊,就同步把它加進 UI Library,讓這頁隨著開發長大,一個區塊沒進 UI Library 就算沒完成,稽核時會被抓出來。

那內建的「樣式 → 區塊」清單不就夠了嗎

做到這裡你可能會想到:網站編輯器的「設計 → 樣式 → 區塊」本來就有一份區塊清單,點進去每個區塊都能調顏色、字型、間距,那不就是內建的 UI Library 嗎?

先講一個你八成會遇到的狀況:打開那份清單,自己做的區塊多半一個都不在裡面。原因是至少要有一個樣式面板區塊才會出現在那份清單裡,而面板存不存在,取決於 block.jsonsupports。如果你的區塊只宣告了 htmlalign,這兩個都不產生樣式面板,於是它就被 return null 掉了而不會出現。

要讓它出現,就把樣式類的 supports 開起來。文字型的區塊我會這樣開:

"supports": {
	"html": false,
	"align": false,
	"color": { "text": true, "background": true },
	"typography": {
		"fontSize": true, "lineHeight": true, "textAlign": true,
		"__experimentalFontWeight": true, "__experimentalLetterSpacing": true
	},
	"spacing": { "margin": true, "padding": true }
}

卡片型的則是 colorspacing__experimentalBordershadow。注意邊框那個鍵名是 __experimentalBorder 不是 border,核心的 group、image、columns 到現在都還這樣寫。另外 render.php 要有 get_block_wrapper_attributes(),面板調出來的值才會真的套到 markup 上。

但它跟 UI Library 解決的不是同一件事

補完 supports 之後,你的區塊確實都會出現在那份清單裡,但它取代不了 UI Library:

樣式 → 區塊清單 UI Library 頁面
一次看到 包含所有區塊 自訂區塊
看到的內容 example 的單一預覽 真實內容渲染的每個變體
用途 調樣式 檢視一致性、驗收
給客戶或設計師看 要進後台逐個點 一個網址捲一遍看完
改動存到哪 資料庫(wp_global_styles 不改任何東西,純檢視

你想比較「三張卡片的圓角一不一致」或是「主色出現的地方協不協調」時,那份清單幫不上忙,它一次只給你看一個區塊,而且看的是 example 那筆假資料。

最後一列的差別更值得留意:樣式面板改出來的東西存在資料庫wp_global_styles),它會蓋過 theme.json,這就是之前談過的「檔案與資料庫誰是真相來源」,這代表你每開一個樣式 supports,等於多開一道讓人繞過 theme.json 的門。

所以開哪些 supports 是設計決策不是技術問題:**哪些區塊你願意讓網站擁有者微調、哪些要鎖死,**一個參考的分法是最小元件(badge、tag、button)開 color 加 typography,版面型區塊(hero、page-header)只開 spacing,卡片型看客戶要不要能換底色。

範本其實就是區塊的堆疊

檢視完、修掉 UI 問題才輪到組頁面,區塊佈景主題的首頁是 templates/front-page.html,它不是 PHP 而是一份區塊標記,組首頁的動作本質上就是「把區塊依順序疊起來,用容器區塊排好版」:

<!-- wp:template-part {"slug":"header","tagName":"header"} /-->

<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">

  <!-- wp:block-theme/hero {"align":"full"} /-->

  <!-- wp:group {"align":"full","backgroundColor":"surface","style":{"spacing":{"padding":{"top":"var:preset|spacing|80","bottom":"var:preset|spacing|80"}}}} -->
  <div class="wp-block-group alignfull has-surface-background-color has-background">
    <!-- wp:block-theme/section-head {"eyebrow":"SERVICES","heading":"服務項目"} /-->
    <!-- wp:columns -->
      ...
    <!-- /wp:columns -->
  </div>
  <!-- /wp:group -->

</main>
<!-- /wp:group -->

拆開來看結構很單純,最上面用 wp:template-part 拉進 header,主體用一個 wp:groupmain 容器,裡面依序放 hero、一個帶背景色的 group 包住服務項目、再一個個往下疊。每個自訂區塊就是一行 wp:block-theme/xxx,核心的 wp:columnswp:group 負責分欄和分區。

那個 wp:group 的 padding 寫的是 var:preset|spacing|80,背景色是 surface,它們不是隨手打的 48px#f6f7f8,而是指向 theme.json 裡定義好的間距刻度和顏色。

這就是區塊佈景主題的最大好處,因為每個區塊、每個容器用的都是定義好的樣式值,這個首頁不含任何一個寫死的顏色和尺寸,哪天客戶說「主色換一下」,你改 theme.json 一行整個首頁跟著換,不用進來這份 front-page.html 找二十個地方。

用 AI 組頁面:你出結構,它出標記

手寫這串巢狀的區塊標記很累,括號、tagName、class 一個對不上就壞,這正是 AI 最擅長的地方。我組首頁時的做法是把「版面結構」用自然語言描述給 Claude Code:

首頁從上到下:header、一個滿版 hero、一區服務項目(section-head 標題加三欄 service-card)、一區「這些情況我幫得上忙」(兩欄 check-item)、最後 CTA。服務區給淺灰背景,每區上下留 80 的間距。

它會把上面那種區塊標記整段生出來,wp:groupwp:columns 的巢狀關係、var:preset 的間距都照 theme.json 的值填好。你做的是「決定版面長怎樣」這個設計層的事,繁瑣的標記交給它。

組完後一定要驗的兩件事

AI 組頁面很快,但有兩個地方我一定會回頭檢查。

第一,有沒有偷塞 inline style。AI 有時為了「快點達到你要的效果」,會直接寫一段 style="margin-top:37px" 這種寫死的值,一旦出現設計樣式的一致性就破了個洞,看到就要它改回 var:preset 的屬性。

第二,滿版和內容寬度對不對。hero 這種要通欄的用 align:full,內文區塊要收在 contentSize 裡,這兩個混亂的話頁面會忽寬忽窄。

這整條流程可以一鍵跑完:make-block

到這裡你已經看過完整流程了:抽取設計元素、寫進 theme.json、一個個做區塊、產出 UI Library、組成頁面。Everything-WP 把這條路做成了一個指令 /make-block,輸入是一個參考網址或幾張設計稿:

/everything-wp:make-block https://example.com
/everything-wp:make-block mockup-home.png mockup-about.png
/everything-wp:make-block https://example.com mockup-home.png   # 混合輸入也可以

它會跑完五個階段,中間不需要你確認,直到最後才停下來等你看:

Stage 1|抽取:先讀 robots.txtsitemap.xml 把整站頁面盤點出來,依網址分類成 home/page/archive/single,並把同一個範本的頁面歸成一群,例如多篇 /blog/{slug} 只會挑一篇代表來掃。

這一步很關鍵,掃 7 個代表頁跟掃 1.000 頁的差別,就是這條流程跑不跑得完。接著用 agent-browser 打開每個代表頁,抓 computed style 得到精確的 CSS 值,並依「共用元素 → 頁面區段 → 最小元素」三個層次盤點元件,避免 hero 裡的按鈕被重複抽成兩個元件。

Stage 2|theme.json 與工具鏈:呼叫 /init-theme 把主題骨架、PHPCS、PHPStan、CI、建置腳本立起來,然後把抽到的原始值正規化:顏色分群後依角色命名(basecontrastprimaryaccent…)收斂成 6 到 10 個語意色、字級對到刻度、間距推成一套 scale。

Stage 3|生成區塊:盤點到的每個元件生成一個動態區塊(block.json + render.php + edit.js + style.css),硬規則是區塊 CSS 不准出現十六進位色碼和寫死的 px、不准有重複的 markup,每完成一個就同步補進 UI Library。

Stage 4|UI Library 頁面:產生 patterns/ui-library.php,在本機站台建一個草稿頁,把前台和編輯器兩個網址交給你,這是整條流程唯一的人工關卡。

Stage 5|一致性稽核:跑腳本檢查寫死的色碼與 px 數、重複的 markup 結構、盤點清單裡有沒有元件漏做,再跑 composer phpcs && composer phpstan,結果寫進 design/consistency-report.md

跑完之後 design/ 目錄留下的東西,是我覺得這條流程最有價值的部分:

design/
├── page-inventory.json      # 掃了哪些頁、怎麼分群的
├── design-tokens.raw.json   # 抽到的原始值
├── token-mapping.md         # 原始值 → 語意名稱的對照與理由
├── component-inventory.md   # 元件清單、變體、在哪幾頁出現
└── consistency-report.md    # 稽核結果

這個系列用的 block-theme 就是這樣生出來的,掃的是我自己的部落格。token-mapping.md 裡會這樣記:

| 原始值(使用次數最高)        | 語意名稱            | 出現位置                    |
| rgb(255,210,77) / rgb(255,220,115) | `primary` #ffd24d | logo、標題強調、序號、圓點、底線 |
| rgb(245,124,0)               | `accent` #f57c00   | 分類標籤、連結、箭頭          |
| rgb(239,239,239)             | `border` #efefef   | 卡片與分隔線                 |

捨棄(雜訊/出現次數低):白色半透明 0.7–0.78 — 深色 hero 上的覆蓋文字;
rgb(160,178,185) — 深色卡片上的灰藍 meta。兩者都可用 base/muted 在深底上表達。

它不只給你結果,還記下「為什麼把這兩個相近的黃色合併成一個」「哪些值被當雜訊丟掉」。這份稽核軌跡的用途是,當你三個月後覺得某個顏色怪怪的,可以回頭查它是從哪個網頁的哪個地方來的。

這條流程真正的規則:單一來源

/make-block 有一條寫死在指令裡的規矩,它叫做「憲法」,白話講就是每個視覺值只有一個來源,頁面只能引用、不准重新定義:

視覺項目 唯一來源 頁面/範本裡禁止
顏色、字級、間距 theme.json 的屬性 寫死的 hex 或 px
元件的 markup 與樣式 動態區塊與它的 CSS 複製 markup、蓋 class
頁面 範本(只引用) inline style、重新定義元件

這條規矩帶來的好處在 review 階段最有感,你看著 UI Library 頁面說「主色太暗」,要改的是 theme.json 裡的一個值,不是二十個檔案;說「按鈕的 hover 不對」,改那個區塊的 CSS,所有用到它的地方一起更新。永遠不要用「在別的地方多加一段樣式」來修視覺問題,那等於在單一來源上鑿一個洞。

但組頁面這一段,它刻意不自動化

/make-block 到 UI Library 就停了,後面的頁面組裝是對話式的,因為組頁面牽涉的全是判斷題:

你的需求 實作方式
這區塊要列出最新 N 篇文章 區塊 render.phpWP_Query,attributes 給 postType、數量、分類篩選
這段文字或按鈕要能在後台改 做成區塊 attributes,每個實例各自編輯
全站共用的設定(電話、社群連結) 產一個設定頁,區塊讀 get_option()
文章要多一個欄位並顯示在元件上 register_post_meta 加編輯器面板,區塊在 render.php 讀 meta

同樣是「這裡放三篇文章」,可能是寫死的三個區塊、可能是 Query Loop、也可能是區塊自己跑 WP_Query,選哪一種取決於誰要維護、多久改一次、要不要客戶自己能調,這種問題沒有標準答案,AI 猜不出來,只能你告訴它。

一個心態上的轉變

走完這一輪你會發現,用區塊佈景主題加 AI 做網站,跟傳統主題「切一個 PSD 成範本」是很不一樣的體驗。你不再是像素級地刻每個區塊,而是在決定用哪些現成的區塊、怎麼編排、要留多少間距,剩下的讓 AI 去組合。

而且這套資產不綁死在這個站。區塊的結構和行為blocks/ 裡的 block.jsonrender.phpedit.js)可以整包搬到下一個專案,設計的長相theme.json 的顏色、字級、間距)換一份就好。同一個 service-card,在 A 站是黃底黑字、在 B 站換成藍底白字,區塊程式碼一行都不用改。UI Library 就是你檢視這套可複用資產的窗口,也是交接給下一個專案時最好的說明書。

頁面組好了、設計系統也齊了。不過這一路我們都在做一個全新的主題,而真實世界裡你手上更可能是一個跑了三年的傳統佈景主題,客戶也不會給你打掉重練的預算。下一篇換個場景,談怎麼把舊主題分三個階段搬進 Block Theme,而且每一步都能單獨上線。

文章目錄:oberonlai.blog/category/2026-ithome


上一篇
WordPress Interactivity API:用 AI 做一個客戶評價輪轉
下一篇
把舊的 WordPress 傳統佈景主題搬進 Block Theme
系列文
從一句話到一個網站:用 Vibe Coding 開發 WordPress Block Theme 的 30 天29
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言