こんにちは!プロダクトデザイナー、そしてフロントエンドエンジニアの皆さん。日々のデザインシステム運用の現場で、こんなストレスを感じたことはありませんか?
「Figma(またはPenpot)で作ったデザインシステムと、実際のReactコードの間に見えない壁がある」
「デザイナーがマージンやカラーをちょっと変えただけで、エンジニア側でCSS変数やTailwindの設定を全手動で直すハメになる」
「アクセシビリティ(a11y)を考慮したUIを作りたいのに、ARIA属性の付け忘れやキーボード操作のバグが後を絶たない」
もし、あなたがこの泥臭いワークフローに嫌気が差しているなら、朗報です。
オープンソースの次世代デザインツール「Penpot」と、WAI-ARIAに完全準拠したロジックだけを提供する「Headless UI」(あるいはTailwind CSS周辺エコシステム)を融合させることで、「デザインの変更がそのままアクセシブルなReactコードの更新に直結するパイプライン」を構築できます。
これをマスターすれば、毎日の作業が劇的に楽になりますよ。今日は、その魔法のような仕組みの裏側と、具体的な実装ステップを一緒に紐解いていきましょう。
—
1. なぜ「Penpot × Headless UI」なのか?
まず、このアーキテクチャの思想を整理しておきます。
- Penpotの役割(真実のソース:Single Source of Truth):
視覚的なデザインだけでなく、「デザイン変数(Design Tokens)」をCSS VariablesやJSONとしてエクスポートできるオープンソースの強力なツール。プロプライエタリなツールと違い、APIやデータ構造がオープンであるため、コードの世界と親和性が異常に高いのが特徴です。
- Headless UIの役割(アクセシビリティの担保):
スタイルを一切持たず、モーダルやドロップダウン、タブなどの「振る舞い(State)」と「アクセシビリティ(ARIA属性・キーボードナビゲーション)」だけを提供するReactコンポーネントライブラリ。
この2つを「デザイン変数」という共通言語で直結させます。デザイン側で定義されたトークンがコードの型やプロパティになり、Headless UIがアクセシブルなDOMを生成する。この流れを作ると、「デザインの意図がそのままコードの品質になる」という理想郷が完成します。
—
2. 環境構築:Penpotとプロジェクトの初期設定
それでは、実際に手を動かしてこのパイプラインの土台を作りましょう。今回は最もモダンな構成として、Vite + React + TypeScript + Tailwind CSS + Headless UI の環境を想定します。
ステップ①:プロジェクトの雛形を作る
まずはターミナルを開き、Viteを使って超高速なReact開発環境を立ち上げます。
プロジェクトの作成と移動
npm create vite@latest penpot-headless-lab — –template react-ts
cd penpot-headless-lab
必要な依存関係のインストール(Headless UIとアイコン用ライブラリ)
npm install @headlessui/react clsx tailwind-merge
npm install -D tailwindcss postcss autoprefixer
ステップ②:Tailwind CSSの設定
Tailwindの初期設定ファイルを作成します。
npx tailwindcss init -p
生成された `tailwind.config.js` を次のように編集してください。ここに、後ほどPenpotからエクスポートする「デザイン変数」をマッピングしていきます。
// tailwind.config.js
/ @type {import(‘tailwindcss’).Config} /
export default {
content: [
“./index.html”,
“./src//.{js,ts,jsx,tsx}”,
],
theme: {
extend: {
// Penpotのデザイン変数(Design Tokens)と同期させる場所
colors: {
brand: {
primary: ‘var(–color-brand-primary, #3b82f6)’,
background: ‘var(–color-brand-bg, #ffffff)’,
}
},
borderRadius: {
pill: ‘var(–radius-pill, 9999px)’,
}
},
},
plugins: [],
}
—
3. Penpotのデザイン変数をコードに落とし込む
Penpotでは、カラーやスペーシングを「Design Tokens(CSS Variables / JSON)」形式でエクスポートする機能が備わっています。
例えば、Penpot側で定義した変数を、プロジェクト内の `src/styles/tokens.css` として配置します。
/ src/styles/tokens.css /
:root {
–color-brand-primary: #6366f1; / PenpotのIndigo 500と同期 /
–color-brand-bg: #f8fafc; / Slate 50と同期 /
–radius-pill: 12px;
}
これを `src/index.css` で読み込みます。
/ src/index.css /
@import ‘./styles/tokens.css’;
@tailwind base;
@tailwind components;
@tailwind utilities;
これで、Penpot上のデザイン変更が、CSS変数を介して一瞬でコード側に反映される土台ができました。
—
4. HelloWorld的実装:アクセシブルなカスタムボタンの作成
理論はこれくらいにして、実際に「Penpotのデザイン変数」をまとわせた、Headless UIベースのアクセシブルなメニュー(Menuコンポーネント)を作ってみましょう。
Headless UIの `Menu` は、スクリーンリーダー対応や、「Escキーで閉じる」「矢印キーで項目を移動する」といった面倒なインタラクションを最初から完璧に内包しています。
以下のコードを `src/components/AccessibleMenu.tsx` として作成してください。
// src/components/AccessibleMenu.tsx
import React from ‘react’;
import { Menu, Transition } from ‘@headlessui/react’;
import { Fragment } from ‘react’;
export const AccessibleMenu: React.FC = () => {
return (
);
};
そして、これを `src/App.tsx` で呼び出します。
// src/App.tsx
import { AccessibleMenu } from ‘./components/AccessibleMenu’;
function App() {
return (
Penpot × Headless UI パイプライン
デザイン変数とアクセシビリティが完全に調和したコンポーネントです。
);
}
export default App;
アプリケーションを起動してみましょう。
npm run dev
ブラウザで確認すると、Penpotで定義したカスタムカラーと角丸(`rounded-pill`)を纏いながら、キーボードの上下キーやTabキー、Escキーで完璧に操作できるドロップダウンメニューが動いているはずです。アクセシビリティの面倒な実装を一切自前で書くことなく、高品質なUIが爆速で手に入りました。
—
5. 精度を高めるための実務のコツ
この構成を実際のプロダクションチームで運用する際、さらに開発体験を爆発的に高めるための「プロの知見」をいくつか授けます。
1. Design Tokensの自動同期パイプライン化
PenpotのプラグインやAPIを活用し、デザイン変数が更新されたらGitHub Actions経由で自動的にJSONやCSS変数(`tokens.css`)へ変換・PRを作成する仕組みを作りましょう。これにより、「デザイナーが色を変えたのに、エンジニアが気づかない」というヒューマンエラーがゼロになります。
2. Headless UIの「スタイル責務」の分離
Headless UIのコンポーネント自体に直接Tailwindクラスを書き散らすのではなく、共通のラッパー(Compound Componentsパターンなど)を作成し、そこにデザイン変数を適用するのがベストプラクティスです。コンポーネントの構造がスッキリし、デザインの変更に対する耐性が跳ね上がります。
—
おわりに
Penpotという自由でオープンなデザインプラットフォームと、Headless UIという堅牢なアクセシビリティのプリミティブ。この2つをデザイン変数で繋ぐアプローチは、これからのモダンフロントエンド開発のスタンダードになっていくと確信しています。
「デザインとコードの乖離」に悩む日々に、今日で終止符を打ちましょう。
あなたのプロダクト開発が、もっと滑らかで、もっとクリエイティブなものになりますように!