GDG Apps の正式なWebデザインシステム。React 19、Radix Primitives、CSSトークンを基盤とし、claymorphismの柔らかな立体感、Light/Dark、アクセシビリティ、控えめなアニメーションを共通化します。ルーター・認証・データ取得から独立したprivate workspaceです。
角丸は --gdg-radius-* の半径値と --gdg-corner-shape: squircle を一つの契約として扱います。components.css は gdg-* 要素とその子孫を同じ半径スコープに置くため、有限半径を追加するときは border-radius だけを追加すればSquircleになります。円・pill・角丸なしの形状は明示的に維持し、corner-shape 未対応ブラウザーでは border-radius の標準形状へフォールバックします。
正本は gdg-jp/design-system です。単独開発ではこのリポジトリのルートで pnpm install 後、以下を実行します。
gdg-jp/gdgjp では design-system/ submodule として配置します。親で git submodule update --init design-system を実行してから pnpm install してください。UI の変更はこのリポジトリへ先に commit・push し、親で git add design-system して参照コミットを更新します。
| コマンド | 内容 |
|---|---|
pnpm --filter @gdgjp/design-system dev |
Storybook、port 6006 |
pnpm --filter @gdgjp/design-system build |
ESM、型宣言、CSS、フォントをdistへ出力 |
pnpm --filter @gdgjp/design-system typecheck |
実装・Storybookの型検査 |
pnpm --filter @gdgjp/design-system test |
公開API・SSR・色のコントラスト |
pnpm --filter @gdgjp/design-system test:consumer |
ビルド済み公開exportのみでSSR・型解決・ブラウザービルド |
pnpm --filter @gdgjp/design-system test:e2e |
Storybook/consumerをビルドしてPlaywright・axe・画像比較 |
ブラウザーの初回準備は pnpm --filter @gdgjp/design-system exec playwright install chromium。生成物はコミットしません。画像比較の基準画像はテスト資産として管理します。
親モノレポの staged CI は、型検査・単体テスト・ビルドを Turbo の依存関係に沿って並列実行します。
CI 内部の build:e2e は型宣言の生成に --noCheck を使い、別タスクの完全な型検査が成功してからブラウザーテストを開始します。
build:storybook:test はすべての Story・Docs を残し、prop 推論とソースマップだけを省きます。
test:e2e:browser はビルド済み成果物を使うため、単独で一式を検証する場合は従来どおり test:e2e を使ってください。
CI のブラウザー実行は最大6並列で、トレースは失敗後の最初の再試行で記録します。
ビルドには Vite 8、型検査と宣言生成には native TypeScript 7 を使います。
Storybook のドキュメント生成向けに TypeScript 6 の API も保持します。
親の staged CI は新旧の submodule commit を比較し、単体テストだけの変更では関連する単体テストと型検査を実行します。
E2E spec だけの変更は GDG_UI_E2E_FILES(JSON 配列)で対象を渡し、実装・CSS・設定などの変更は全件検証します。
PLAYWRIGHT_JSON_OUTPUT_NAME を指定すると、通常の進捗表示とあわせてテスト別の実行時間を JSON に保存します。
利用アプリのdependenciesに "@gdgjp/design-system": "workspace:*" を登録します。親モノレポ内では design-system/ を pnpm workspace として引き続き利用します。
import { ThemeProvider, Button, FormField, Input } from "@gdgjp/design-system";
import "@gdgjp/design-system/tokens.css";
import "@gdgjp/design-system/components.css";
import "@gdgjp/design-system/fonts.css";
export function App() {
return (
<ThemeProvider>
<main className="gdg-preview">
<FormField label="表示名" description="参加者に表示される名前です。" required>
<Input name="displayName" />
</FormField>
<Button type="submit">保存</Button>
</main>
</ThemeProvider>
);
}fonts.css は任意の独立した読み込みです。同梱するGoogle SansとNoto Sans JPはOFL配布物です。フォントの出典・ライセンスは assets/FONTS.md を参照してください。
@layer theme, base, gdg-tokens, gdg-base, gdg-components, utilities;
@import "tailwindcss";
@import "@gdgjp/design-system/tailwind.css";
@import "@gdgjp/design-system/components.css";
@import "@gdgjp/design-system/fonts.css";tailwind.css はトークンと @theme inline の対応表です。Tailwind本体やPreflightは含みません。上の例では利用側が tailwindcss のimportによってPreflightを選択しています。リセット不要ならTailwindのtheme.cssとutilities.cssだけを読み込んでください。
コンポーネント自身は通常のCSSクラスを使い、Tailwindのソース探索を必要としません。利用側は bg-background text-foreground bg-primary text-primary-foreground bg-secondary text-secondary-foreground bg-gdg-red 等を使えます。色は --gdg-* を正本とし、意味トークンを参照します。secondary は緑寄りの低彩度ブルー(#5D899D)に白い前景色を合わせ、hover は #6B9AAE です。primaryの鮮やかな青と役割を分け、補助操作であることが分かるトーンにしています。hover は利用側で --gdg-secondary-hover を上書きして変更でき、従来の中立面には bg-gdg-neutral を使います。レイヤー順を最初に宣言することで、利用側utilitiesが部品CSSを上書きできます。
ThemeProvider は文書単位で一つ配置します。既定は system、保存キーは gdg-apps-theme。useTheme() はnext-themesの theme / resolvedTheme / setTheme を返します。ThemeToggle はアイコンボタンからシステム・ライト・ダークを選ぶラジオメニューです。従来の native select から変更しているため、操作テストではボタンを開いて menuitemradio を選択してください。
AppShell の footer はデスクトップのサイドバー下部とモバイルのナビゲーションSheetに表示します。header を省略するとデスクトップのヘッダーは表示されません。collapsible="none" では開閉ボタンを表示しません。mainClassName で本文の幅や余白を指定できます。
Input と SelectTrigger は className の高さ指定を尊重し、Textarea は native rows(既定4)に従います。Input / Textarea は既定で親幅を使い、Label はブロック表示です。coarse pointer の44pxの操作領域は維持します。Icons の size は外側とSVGの両方に適用され、className のサイズ指定もSVGに反映されます。IconButton の size="sm" は32pxの正方形です。
SSRでは <html lang="ja" suppressHydrationWarning> を使用してください。next-themesの初期化スクリプトが最初の描画前にhtmlのclassを設定します。CSP使用時はリクエストごとのnonceを ThemeProvider nonce={nonce} に渡します。Provider外の先行したテーマ依存UIを避け、テーマ値によってDOMを変える部分はマウント完了まで安定した内容を出力してください。
Portalはdocument.bodyへ配置し、htmlのテーマを継承します。部分ツリーごとの別テーマは提供しません。forcedTheme はカタログ等の固定表示専用です。全体のテーマ変更に遷移アニメーションはありません。
| 分類 | Exportと用途 |
|---|---|
| 操作 | Button, IconButton, Link, ButtonGroup, Collapsible, Slider, Toggle, ToggleGroup。Buttonはprimary/secondary/outline/ghost/danger、sm/md/lg。既定typeはbutton。IconButtonのaria-labelは必須 |
| 表示 | Text(xs/sm/md、default/muted)、Heading(level 1〜6)、Stack, Inline, Card, Separator, Badge(neutral/info/success/warning/danger)、Avatar, Attachment, Bubble, Chart, Empty, Icons, Item, Kbd, Marker, Message, Typography |
| フォーム | FormField, Field, Input, InputGroup, InputOTP, Textarea, Checkbox, RadioGroup+RadioGroupItem, Switch, Select, NativeSelect, Calendar, DatePicker, Combobox, Command, Questionnaire, QuestionnaireNew |
| オーバーレイ | Dialog, AlertDialog, Sheet, Drawer, Popover, DropdownMenu, ContextMenu, HoverCard, Menubar, Tooltip と対応するTrigger/Content、必要なTitle/Description/Close/Action/Cancel/Item。TooltipProviderも公開 |
| 状態 | Alert(tone、title、children)、Progress, Toast, Toaster, toast, Spinner, Skeleton, EmptyState(title、description、action) |
| 構造 | AspectRatio, Table, DataTable, Pagination, Tabs+List/Trigger/Content、Accordion+Item/Trigger/Content、Breadcrumb, NavigationMenu, Resizable, ScrollArea, Sidebar, Toolbar, PageHeader, AppShell, SidebarNav, Direction |
| テーマ | ThemeProvider, ThemeToggle, useTheme |
| 補助 | cn。clsx+tailwind-mergeによるclassName合成 |
Radix部品はRadixのcontrolled/uncontrolled props、イベント、refを維持します。DOM部品はネイティブ属性とrefを受け取ります。コンポーネント内部のDOM構造に依存したセレクタは公開契約ではありません。
Combobox は query/open/value/activeValue をそれぞれ controlled または uncontrolled として
扱います。query の controlled empty は query=""、選択値と active option の controlled empty は
value={null}/activeValue={null} です。prop を省略した場合だけ内部 state を使い、mount 後の
controlled/uncontrolled 切り替えによる state 移行は行いません。defaultQuery、defaultActiveValue、
onQueryChange、onActiveValueChange で初期値と通知を指定できます。
既定の shouldFilter は true、closeOnSelect は true です。リモート検索では
shouldFilter={false}、複数選択では closeOnSelect={false} を明示し、取得・debounce・ACL・chip state は
利用側が所有します。入力は popup 開始時に focus を受け、候補は aria-activedescendant で示されます。
ArrowUp/ArrowDown/Home/End は表示中かつ enabled の候補だけを端で停止して移動し、Enter は現在の
候補だけを選択します。Escape は popup だけを閉じ、Tab は通常の focus 順序を保ちます。
Icons の IconName と IconBaseName は package root から type export されます。animated icon と static
catalog icon は同じ Icons wrapper、ARIA、size、style、strokeWidth、ref handle を共有します。
static icon の startAnimation()/stopAnimation() は安全な no-op で、animateOnHover によって source を
切り替えません。意味を持つ単独 icon には aria-label、装飾 icon には aria-hidden="true" を指定します。
DatePicker は value prop の key が存在するかで controlled mode を判定します。value を省略した場合は
defaultValue と内部更新を使い、value={undefined} は過去の default や internal selection を表示しない
controlled empty です。Calendar も同じ規則で selected={undefined} を controlled empty として扱います。
外部 clear/replace は input、selected day、invalid draft と表示月へ同期しますが、日付は timezone-aware
timestamp へ変換せず local year/month/day として扱います。
- Buttonのloadingはaria-busyと二重実行防止、通常ボタンではSpinnerを伴います。disabledはネイティブdisabled、asChildではaria-disabled・tab順序・クリック抑止に対応します。asChildは属性とイベント、refを転送する一つの要素を渡してください。リンク合成時のloading内容は呼び出し側で用意します。
- FormFieldは一つの入力を包みます。label/説明/エラーをidで結び、required/disabled/invalidを渡します。明示idはFormFieldに指定します。
name、検証ロジック、送信処理は利用側が所有します。 - Input/Textareaのreadonlyは選択・コピー可能なまま、disabledは操作不可。フォーム送信への影響はHTML標準に従います。Radix Selectのrequired/disabledはRootへも伝播します。
- DatePickerのテキスト入力は数字を順に受け付け、クリック時に入力全体を選択します。年/月の区切りは自動挿入され、
/は入力せずにYYYY/MM/DDへ整形されます。 - RadioGroupにはFormFieldまたはaria-label/aria-labelledbyでグループ名を付け、各RadioGroupItemにもlabelを付けます。複数の別入力を一つのFormFieldに入れません。
- Dialog/SheetにはTitleとDescription、AlertDialogにはTitle・Description・Cancel・Actionを配置します。内容・Escape・Tab・フォーカス復帰はRadixの契約に従います。Triggerなしのcontrolled表示では
onCloseAutoFocusで復帰先を明示します。 - Tableはcaption、thead/tbody、thのscopeを利用側で指定します。横幅が足りない場合は表の領域内でスクロールし、
scrollLabelで領域名を変更できます。 - DataTableは見出しと全セルの表示内容から列ごとの最大幅を求め、比率で既定の列幅を配分します。
- Breadcrumbはliをchildrenに渡し、末尾にaria-current="page"。SidebarNavはリンクをchildrenに渡し、選択中リンクにaria-current="page"。URL判定はライブラリに持ち込みません。
- Sidebarは既定で
collapsible="icon"です。SidebarHeader内のSidebarTriggerはタイトル右側の正方形アイコンボタンになり、閉じた状態ではメニュー項目のアイコンを残します。完全に収納する場合はcollapsible="offcanvas"を指定します。 - Toolbarは配置部品です。矢印キー操作を提供しないためARIA toolbarロールは付けません。
- Skeletonは控えめなshimmerが走る装飾プレースホルダーです。読込状態はSpinnerのlabelか親のaria-busyとメッセージで通知し、reduced motionではshimmerを停止します。情報・成功・警告・危険は色とラベル/アイコンで表します。
Iconsはlucide-animatedを内部でラップしたアイコン部品です。使用側は@gdgjp/design-systemからIconsを読み込み、Lucideの名前(例:name="Heart")を指定します。既定ではanimateOnHoverが有効で、カーソルホバー時にアイコン固有のアニメーションを再生します。animateOnHover={false}で停止でき、prefers-reduced-motionでは装飾的なホバーアニメーションを停止します。ラベル付きアイコンはrole="img"を自動付与し、操作部品内の装飾アイコンにはaria-hidden="true"を指定してください。
詳細な判断基準・変更手順は DESIGN.md、組み合わせ例と状態一覧はStorybookを参照してください。