元件

registry 11 個 · 型錄 32

元件層兩層並存:React + Tailwind v4(shadcn registry)與已凍結的 vanilla CSS class(components.csscomponents.js)。取用前先確認要哪一層。

React + Tailwind v4

ds-select(live demo)

直接 import registry/hhg/select.jsx 渲染,Tailwind utility 由 tokens.json 生成的 @theme inline 供應。

HHG Select@hhg/ds-select):HHG Design System 的 Select(trigger + 白底 menu)。形態乙:React + Tailwind v4 utility,從 components.css 的 .ds-select 族與 components.js 的 DS.initSelect 遷移,逐屬性等值驗收通過。v1.18.0 新增選單向上展開(自動翻轉 + forceDirection 覆寫,行為契約 SEL-O8–O13)與 groupLabel prop。⚠️ 本檔同時是 ds-combobox 的樣式與 hook 來源(cx/ROOT/TRIGGER/OPTION/useFlipDirection/useDismiss 皆由它匯出)。

目前值null(尚未變動)DS_VERSION v1.18.0
預設(受控)
h-select / r-btn 幾何;點開會有勾選標記、計數徽章與停用項。⚠️ v1.18.0 起**不再**有那一列「分組標題」——那是 v1.17.0 期間寫死的量測樣本殘留,現在要傳 groupLabel 才出現
欄位情境 inField
h-input / r-input 幾何,寬度撐滿父容器
錯誤態 invalid
邊框換 --red,其餘幾何不變
分組標題 groupLabel
v1.18.0 新增。預設 null =不渲染那一列;傳字串才會在選單頂部出現 .ds-option-group。左邊那顆沒傳、右邊那顆傳了,點開對照看
向上展開 forceDirection
v1.18.0 新增。預設 'auto'=量測後決定(下方空間不足就往上);'up'/'down' 是覆寫,不量測
程式面 setter/getter
對應舊世界的 el._ds.setValue/getValue(imperative handle 恆定,不會 stale)
React + Tailwind v4

ds-button(live demo)

.ds-btn 五個變體 + .ds-btn-icon.ds-btn-sm.ds-dashed-btn + 忙碌態 + 深色外框,共 30 條規則。本族沒有行為層components.js 無對應的 DS.init*)。

HHG Button@hhg/ds-button):HHG Design System 的按鈕族(Button/IconButton/DashedButton)。形態乙:React + Tailwind v4 utility,從 components.css 的 .ds-btn 系+.ds-btn-icon+.ds-btn-sm+.ds-dashed-btn 遷移,281,792 次 computed 屬性逐一比對、白名單外零差異。按鈕族無行為層(components.js 沒有對應的 init)。⚠️ 填色三變體(primary/danger/cta)的 hover/active 在 React 版統一成一條 OKLCH 明度式,與 vanilla 刻意不同。

⚠️ 填色鈕 hover 在 React 版統一過,與下方 vanilla 型錄刻意不同:三個填色變體 (primary/danger/cta)統一成 oklch(from <底色> calc(l + (1 - l) * 0.18) c h)。 係數 0.18 是以 --ink → --ink-hi 校準。components.css 不動。
點擊次數0(尚未點擊)DS_VERSION v1.18.0
五個變體 / toolbar 階
h-btn。primary/danger/cta 是填色鈕,hover 走統一的 OKLCH 明度式;secondary/ghost 是換淺底色
dense 階 size=sm
h-select。挑哪一階看它跟誰同列(.ds-input-sm/.ds-select-trigger/.ds-dashed-btn),不是看它自己重不重要
停用 disabled
opacity .45 + pointer-events:none(點不到,計數不會動)
忙碌 loading
不換文案、不換寬度,只換成轉圈+鎖互動+aria-busy;prefers-reduced-motion 下轉圈停止
按一下看 1.4 秒的忙碌態
圖示鈕 variant=icon
方形,吃 --h-icon;觸控下自動放大到 --hit-min。深底情境用 tone="on-dark"
看起來像按鈕的連結
as="a" + external:補 ↗ 與「在新分頁開啟」的 sronly 提示(只有圖示螢幕閱讀器讀不到)
撐滿寬度 block
側欄主行動鈕:width:100% + 置中(原本靠 .ds-side-cta 父選擇器)
用法
import Button, { IconButton, DashedButton } from '@/components/hhg/button';

<Button variant="primary">主要動作</Button>
<Button variant="secondary" size="sm">退回</Button>   {/* dense 階,與 .ds-input-sm 同列 */}
<Button variant="danger" loading={sending}>刪除</Button>
<IconButton tone="on-dark" aria-label="編輯">✎</IconButton>  {/* 深色外框下 */}
<DashedButton>+ 新增一列</DashedButton>
<Button as="a" variant="secondary" external href="…" target="_blank" rel="noopener noreferrer">
  開啟外部系統
</Button>
React + Tailwind v4

ds-input(live demo)

Input(三個尺寸階)/TextareaField(label 在上)/FieldInline(label 在左)/Search表單用 Field、工具列用 FieldInline,不要互換(GUIDELINE v1.4.0)。

HHG Input@hhg/ds-input):HHG Design System 的 input 族(Input/Textarea/Field/FieldInline/RangeSep/Search)。涵蓋 components.css 的 input 族 26 條規則,365,568 次 computed 屬性比對、白名單 3 筆全部 platform。input 族無行為層。⚠️ 照搬 .ds-input:focus{outline:none}=本族沒有可見的焦點環(庫層待拍板項,不在元件層修)。

🔴 錯誤訊息一律用 error 寫出來,不能只靠 invalid 的紅框。 ⚠️ Textarea 不接受 sizeSearch 沒有寬度政策, 寬度給在外層容器上。
主旨欄位(空)search="" · range=2026-08-01~2026-08-31 · note=0 字DS_VERSION v1.18.0
Field(宣告式 control)
label 在上、控件在中、hint 在下;required 的 * 吃 --req-star,margin-left:2px 由元件給,不用自己打空白
會出現在審核清單的第一欄,建議 20 字內
Field + error
沒傳 invalid 時由 error 推導(有錯誤訊息就是 invalid)。🔴 狀態不只靠顏色——紅框之外一定要寫出錯誤訊息

試著打一個「12,000 元」看紅框與訊息一起出現。

Field + Textarea
control={{ as:'textarea' }};invalid 會自動傳下去
沒有 size 階——庫裡只有一種 textarea
FieldInline(工具列)
label 在左、控件在右;controls 產生的輸入自動吃 size=md + inline(width:auto/130/180),sep 有值就插 RangeSep
Search
庫沒有寬度政策(已知缺登記中)⇒ 寬度由採用端給在外層容器上;輸入框的 props 走 input,刻意不是 type=search
三個尺寸階
form=表單欄位(--h-input,預設)/md=工具列階/sm=密集階。挑哪一階看它跟誰同列
停用與唯讀
原生 disabled/readOnly,元件不另外接管
React + Tailwind v4

ds-badge(live demo)

Badge 五個 tone + StatChip:非互動的顯示型標籤,零狀態規則、零行為。

HHG Badge@hhg/ds-badge):HHG Design System 的狀態標籤族(Badge/StatChip)。涵蓋 7 條規則,68,544 次 computed 屬性比對、白名單 1 筆。本族無行為層。⚠️ 五個 tone 的靜止態文字對比只有 slate 達 WCAG 4.5:1(庫層待拍板項,照搬不修)。

⚠️ 兩者不可互換:Badge=這一列的狀態(語意色、固定 --ds-badge-h觸控下不放大);StatChip=這份清單的統計(無語意色、吃 --h-seg, 觸控下 33→40)。
目前狀態pending / tone=amber共 8 筆 · 顯示第 1-7 筆DS_VERSION v1.18.0
五個 tone
底色與文字色一律由 tone 各自宣告,同一元素上永遠只有一個 bg-* 與一個 text-*(utility 沒有行號可爭)
等待審核tone="amber"等待/處理中
審核通過tone="green"完成/通過
已退回tone="red"退回/失敗
已結案tone="slate"中性/已結案
12tone="default"無語意的計數或分類
換狀態(可互動)
badge 沒有行為層——切換是採用端自己做的。這裡按鈕改 state,Badge 只吃 tone/children
PO-2026-0814 · Q3 倉儲棧板補充(很長的主旨會被壓縮,badge 不會)等待審核
StatChip
吃 --h-seg,與 segmented chip 同列同高。v1.17.0 修過一次漂移:原本寫死 31px(=v1.13.1 以前的 --h-seg)
8顯示第 1-7
換標籤 as
as="li" 之類;Badge/StatChip 都吃(預設 span)
  • 待補發票
  • 已入庫
React + Tailwind v4

ds-tabs(live demo)

Tabs 軌道 + Tab

HHG Tabs@hhg/ds-tabs):HHG Design System 的頁籤族(Tabs/Tab)。涵蓋 6 條規則,159,936 次 computed 屬性比對、白名單 3 筆(含恆等式 --h-tab + 2×--ds-tabs-pad = --h-btn 的兩個 viewport 實測)。⚠️ value/defaultValue/onChange 是 React 版新增的便利層,不是遷移過來的行為(vanilla 的頁籤切換一直由採用端自己做)。

🔴 vanilla 沒有行為層:React 版的 valuedefaultValueonChange新增的便利層。⚠️ 鍵盤刻意不做方向鍵切換與 roving tabindex (與 .ds-seg.ds-pill 同立場)——改用 Radix Tabs 是行為差異, 不是等值替換。
目前分頁mine(尚未切換)DS_VERSION v1.18.0
受控(items 簡寫)
items=[{ value, label, count, disabled }];count 比 tab 文字弱一階(--fs-hint + opacity .75),且 aria-hidden

7 筆等我簽核;其中 2 筆已超過建議回覆時間。

非受控(defaultValue)
不傳 value 時元件自己記;不給 defaultValue 就取 items[0]
自己組 <Tab>
有 children 時忽略 items ⇒ 選中狀態與 onClick 全部自己接(對應 vanilla 的原始用法)
React + Tailwind v4

ds-segmented / ds-pill(live demo)

SegmentedSeg(單選)與 PillsPill(多選),發出 ds-change

HHG Segmented@hhg/ds-segmented):HHG Design System 的分段控制族(Segmented/Seg/Pills/Pill)。涵蓋 14 條規則,144,704 次 computed 屬性比對、白名單 3 筆,另有 docs/behavior/segmented-pills.md 的 26 步行為契約逐步等值(52/52 通過)。⚠️ 守門(onGuardRequest)預設吃 globalThis.DS.guard;要用守門又沒載 components.js 的專案必須自己傳 onGuardRequest。

⚠️ 兩族刻意不同:Segmented 點已選項會短路、不發事件;Pills 每次有效點擊都發事件。 ⚠️ guard 只有 Segmented 有。⚠️ 容器不要掛 data-ds-segmentedcomponents.js 的自動掛載選擇器)—— 同頁再跑 DS.init() 會每次點擊發兩次事件。
區間30(尚未變動) · pills=["shopee","oms"]DS_VERSION v1.18.0
Segmented(單選)
受控。點已選的那顆不會發事件(SEG-O2 短路);disabled 那顆原生 button 本來就不發 click
Segmented + guard
guard 有值即啟用:先開確認型對話框,按確認才切換並發一次事件(SEG-G1/G3/G4)。這裡的對話框由 useDialogs().guard 注入

目前檢視:list。取消時**沒有回呼**(SEG-G5,庫的已知矛盾 ③,照搬不補)。

Pills(多選)
values 由 DOM 順序收集(照搬 initPills 的 querySelectorAll + data-value ?? textContent);全部取消 → 空陣列,仍發事件
自己組 <Seg> / <Pill>
有 children 時忽略 items。Pill 的 readOnly=multicombo trigger 裡的唯讀摘要 chip(<span>,不可點)
← readOnly(唯讀摘要,點不動)
React + Tailwind v4

ds-combobox / ds-multicombo(live demo)

Combobox(單選)與 Multicombo(多選)。兩者共用 select.jsxROOTTRIGGERMENUuseDismiss ⇒ 三族的外點關閉與全域互斥是同一份程式碼。

HHG Combobox@hhg/ds-combobox):HHG Design System 的 combobox 族(Combobox/Multicombo)。涵蓋 combobox 區 21 條 + select 族沿用 23 條,1,148,112 次 computed 屬性比對、白名單 2 筆,另有 docs/behavior/combobox.md 的 82 條行為契約逐步核對(81/83 列通過)。⚠️ 樣式與 dismiss/翻轉 hook 共用 ds-select,不複製一份 —— 兩族在舊世界共用同一組 document listener。

⚠️ 過濾是子字串、大小寫不敏感,同時比 label 與 value,不做模糊比對。 ⚠️ COMBO_VISIBLE_MAX = 50 是模組層常數、不是 prop,超過 50 筆就截。
供應商(單選)(空)(尚未變動) · multi=["v-1002","v-1005"]DS_VERSION v1.18.0
Combobox(單選)
點開後上方是搜尋框、下面是選項;打「田」或「v-100」都會過濾(label 與 value 都比)
欄位情境 inField
h-input/r-input 幾何,寬度撐滿父容器(同 select 的 inField)
錯誤態 invalid
邊框換 --red,其餘幾何不變
Multicombo(多選)
trigger 顯示唯讀 pill 摘要;選單底部有「已選 N 項/清除全部」,過濾出 >1 筆時多一顆「全選」
截斷(>50 筆)
這一顆有 60 個選項:選單只渲染前 50 筆,並在底部說明「請輸入關鍵字縮小範圍」
空清單
emptyText 預設「目前沒有可選的項目」
React + Tailwind v4

ds-table(live demo)

TableCardScrollXTableTheadTbodyTrThTdPaginationPageBtn。結構子元件只發 data 屬性與 min-width, 視覺規則都在 <Table> 根節點 ⇒ 寫裸的 <thead>/<tr>/<th>/<td> 也一樣正確。

HHG Table@hhg/ds-table):HHG Design System 的表格族(Table/TableCard/ScrollX/Thead/Tbody/Tr/Th/Td/CheckCell/Pagination/PageBtn/useStickyColShadow)。涵蓋 components.css 表格族 35 條規則(前綴計數的 19 條低估 84%),另有 docs/behavior/sticky-col.md 的 14 步行為契約逐步等值(14/14 兩側量測值逐字相同)。⚠️ 後代規則一律寫成掛在 <table> 根節點的 arbitrary variant,而 arbitrary variant 不吃 @media (hover: hover) ⇒ 列的 hover 已用 [@media(hover:hover)]: 補那層 media,不要改成內建 hover: 去組(specificity 會翻掉勾選列的階梯)。

⚠️ 庫沒有頁碼演算法.ds-pagination 只有版面,items 簡寫不掛 onClick,切頁邏輯自己寫。🔴 黏右操作欄的左緣陰影靠 useStickyColShadow<ScrollX> 內建): 「有橫向溢出且尚未捲到最右」才顯示,純 CSS 做不到。
目前頁1 / 3(尚未點列)DS_VERSION v1.18.0
TableCard + ScrollX
卡片負責背景/圓角/陰影/裁切;ScrollX 負責橫向捲動與兩側漸層。視窗窄的時候往右捲,看最右邊那欄黏住+左緣多一條陰影
7顯示第 1-3
單號申請人品項金額狀態建立操作
PO-26-0814林品瑄倉儲棧板 ×120128,000等待審核08/14
PO-26-0812陳彥廷出貨紙箱 60×40 ×200074,500審核通過08/12
PO-26-0809何思妤標籤印刷(三色)21,800已退回08/09
宣告式 columns + rows
不想自己排 Tr/Td 時的簡寫。kind:'check' 整格是勾選命中盒(表頭那顆吃 column.checked);kind:'chevron' 是「點列進詳情」的 ›
單號申請人金額
PO-26-0814林品瑄128,000
PO-26-0812陳彥廷74,500
PO-26-0809何思妤21,800
沒有卡的裸表
Table 自己就完整(w-full + border-collapse + 全部視覺規則);卡片與捲動容器都是可選的外層
欄位
本季採購單數24
平均簽核天數2.4
React + Tailwind v4

ds-dialog(live demo)

confirmguard(確認型)、dialog(作業型)、view(作業型的 dismiss-only)。推薦 <DialogProvider> useDialogs();portal 到 document.body不吃父層的 overflow/transform/z-index,仍拿得到 app 樹 context。

HHG Dialog@hhg/ds-dialog):HHG Design System 的對話框族(Overlay/ConfirmDialog/TaskDialog/ViewDialog/DialogProvider/useDialogs/dialogs 模組層橋接/Drawer 六件套)。913,920 次 computed 屬性比對、白名單 8 筆全部 platform,另有 docs/behavior/dialog.md 的 46 步行為契約逐步等值(通過 45/不適用 1/未通過 0)+邊界情境 6 條全過。⚠️ 照搬 vanilla 的立場:沒有 focus trap、沒有 scroll lock、沒有背景 inert。要升級無障礙性請另開拍板,不要換成 Radix Dialog。

⚠️ 兩種型態不可互換:確認型 role=alertdialog、焦點在取消鈕、 不含輸入欄位;作業型 role=dialog、body 可捲、焦點在第一個可填欄位。 🔴 動作先跑、成功才關onConfirmfalse ⇒ 對話框留著可重試。 ⚠️ 模組層的 dialogs.confirm(...) 吃不到 app 樹 context,新程式碼不要用。
最近一次結果(尚未開啟)表單送出的數量:—DS_VERSION v1.18.0
確認型 confirm
role=alertdialog + 焦點落在取消鈕。danger 換紅圖示與 danger 確認鈕;refList 有內容才顯示
onConfirm 回 false
動作先跑、成功才關。先關再跑的話,動作失敗時使用者面前已經沒有可以重試的對話框了
作業型 dialog
role=dialog、body 可捲、焦點落在第一個可填欄位;validate → onConfirm → 關閉,順序固定。取消鈕是 ghost(確認型才是 secondary,庫的已知矛盾 ④)
數量留空或填「十」按送出,看 validate 擋下來
唯讀檢視 view
dismissOnly:只有一顆關閉鈕(預設 primary),焦點落在它身上
React + Tailwind v4

ds-appshell(整頁框架)

AppShellSideMainSideBrandSideNavNavItemAcctSideToggleuseSideDrawer()。整頁 grid + position:fixed 側欄, ≤ SIDE_BREAKPOINT(820px)轉抽屜態並鎖 body 捲動。

HHG App Shell@hhg/ds-appshell):HHG Design System 的整頁框架族(AppShell/Side/Main/SideBrand/SideNav/NavItem/Acct/SideOverlay/SideToggle/useSideDrawer 等 23 個匯出)。涵蓋 components.css appshell 家族 61 條規則,另有 docs/behavior/appshell.md 的 26 步抽屜契約逐步等值(52/52 通過)。✅ v1.18.0 修掉 components.css 兩個 specificity bug:data-shell="plain" 的抽屜原本打不開、prefers-reduced-motion 對 plain 原本失效 —— 兩層已同步修好。⚠️ 刻意不渲染 .ds-side class,讓 React 抽屜與 components.js 的 DS.sideOpen 可以並存不互踩。

⚠️ 本頁不嵌 live demo——它會接管整個 viewport。整頁示範見 採購審核 prototype ↗
Registry

怎麼接進你的專案

components.json 加來源,用 shadcn CLI 取用與更新。 ⚠️ token 跟 Yu Anting 拿——token 無效回的 404 與「元件不存在」完全一樣

1. registry 來源
// components.json
{
  "registries": {
    "@hhg": {
      "url": "https://raw.githubusercontent.com/MIXXINtw/hhg-design-system/main/r/{name}.json",
      "headers": { "Authorization": "Bearer ${HHG_REGISTRY_TOKEN}" }
    }
  }
}
2. 取用與更新
# token 放 .env.local(跟 Yu Anting 拿)
npx shadcn add @hhg/ds-select        # 安裝
npx shadcn add @hhg/ds-button        # 按鈕族
npx shadcn add @hhg/ds-select --diff # 看庫改了什麼(本地客製會被標出)
npx shadcn add @hhg/ds-select --overwrite   # 套用(--yes 不問)
3. 用法
import Select from '@/components/hhg/select';

<Select
  options={[{ value: 'shopee', label: '蝦皮', badge: '3' }]}
  value={value}
  onChange={e => setValue(e.value)}
  placeholder="請選擇來源通道"
  inField      // 欄位情境:吃 --h-input / --r-input 幾何
  invalid      // 錯誤態:邊框換 --red
/>
  • 🔴 版本標記寫成程式碼(export const DS_VERSION),不寫檔頭註解——shadcn add 會刪掉第一個註解區塊。
  • ⚠️ 不要用 git tag 抓 registry;消費方式是 npx shadcn add main 的 raw URL。
  • ⚠️ 採用 Tailwind 前先稽核全域 CSS reset:一條 unlayered 的 * {margin:0;padding:0} 會吃掉全部 Tailwind 間距 utility, 且 build 零錯誤。
Vanilla CSS(已凍結)

HTML 元件型錄 showcase.html

components.csscomponents.js 已凍結、逐族退役中——新專案不要從這裡起手。下面嵌的是 repo 的 showcase.html 原檔,看到的是現值

⚠️ 族數進度 ≠ CSS 退役進度:.ds-select* 那幾條在 ds-comboboxds-multicombo 遷完前不能刪——那兩族根節點 同樣掛 .ds-select
整頁組裝示意 prototype-procurement.html

用同一版元件組出的完整清單頁,資料全部虛構;檔頭註解附缺口清單,已進 roadmap

.ds-appshell深色外框側欄+≤820 抽屜
.ds-page-head頁頭(h1 + 說明鈕 + 副標)
.ds-kpis / .ds-kpiKPI 卡列,含一張 -dark
.ds-toolbar篩選工具列(同列同高)
.ds-search / .ds-input-mdtoolbar 階搜尋與日期區間
.ds-select-mdtoolbar 階篩選下拉
.ds-table / .ds-col-*資料表、欄寬政策、黏右操作欄
.ds-btn-sm列內操作鈕
.ds-badge / .ds-tag狀態 badge 與中性分類 tag
.ds-pagination分頁
.ds-detail-cols / -aux兩欄版面(container query)
.ds-timeline審批進度時間軸
.ds-notif / .ds-menu-panel側欄通知面板
DS.dialog / DS.view新增採購單表單/唯讀審批歷程

版面較寬的章節請用「新分頁開啟」。

型錄章節(32 節)
1. 色票 Color2. 幾何階層 Scale3. 按鈕 Button4. 文字輸入 Input5. 下拉選單 Select5b. 可搜尋下拉 Combobox(單選)/Multicombo(多選)6. Segmented(單選)/ Pill 多選7. Switch / Checkbox8. 欄位卡片 Field Card(三態)9. Tabs / Badge / 統計 Chip / Mono Tag10. 表格 Table + 分頁 Pagination11. 表頭篩選 Popover12. 對話框:兩種型態+唯讀模式 / Toast13. 版面容器:卡片/工具列/空狀態14. 忙碌態:按鈕 loading / 骨架屏15. 提示條 Inline Alert16. 單據詳情:頭區 / 鍵值 / 狀態時間軸17. 觸控目標:由「輸入裝置」決定,不由「螢幕寬度」決定18. 說明鈕 Infotip(v1.5.0)19. 手機列表卡 List Cards(v1.4.0)20. 工具列欄位 Field Inline / 日期輸入的刻意例外(v1.4.0)21. 表格:語意換行 / 欄寬政策 / 黏右操作欄(v1.6.0・v1.7.0・v1.10.0)22. 應用外殼 App Shell + 錨定選單(v1.9.0)23. 圖示 Icons(v1.11.0 規範)24. 頁標題區 Page Head(v1.13.0)25. 中性分類 tag Tag(v1.13.0)26. 兩欄詳情版面 Detail Body(v1.13.0・container query)27. 精靈步驟條 Steps(v1.13.0)28. 黏頂/黏底操作列 Sticky Bar(v1.13.0)29. 通知面板 Notification Panel(v1.13.0)30. 可編輯明細表格 Editable Table(v1.13.0)31. 統計/KPI 卡(v1.13.0)