【テクニカル・上級編】PenpotとHeadless UIの融合:デザインシステムからアクセシブルなReactコンポーネントを爆速で生成する手法 – UI/UX・デザインツール活用バイブル

Penpot × Headless UI: デザインから実装への「完全同期」パイプライン構築論

デザインツールと実装コードの間の「乖離」に、君はまだ絶望しているのか?

Figmaのプラグインエコシステムに依存した「コード生成」は、所詮は表面的なテンプレートの書き出しに過ぎない。我々が真に求めるのは、デザインの意図(Intent)をコードの構造(Primitive)へとロスレスで変換する、堅牢なパイプラインだ。

今日解説するのは、オープンソースの旗手「Penpot」の変数を、Headless UI(Radix UI等)のプリミティブと直結させ、デザイン変更が即座に型安全なアクセシブル・コンポーネントへ反映される、アーキテクトのための自動化手法だ。

—

1. 概念設計:デザイン変数とHeadlessプリミティブの「マッピング」

多くのチームが陥る罠は、デザインツール側のプロパティをそのままCSS変数として書き出そうとすることだ。そうではない。デザインの「意味」を「プリミティブの属性」に射影するのだ。

Penpotの「Design Tokens」を、以下の3階層で定義せよ。

1. Global Tokens: プリミティブな値(`color-blue-500`)
2. Semantic Tokens: 用途(`action-primary-bg`)
3. Component Primitives: Headless UIのPropsに対応する抽象(`button-intent-primary`)

PenpotのAPI経由で取得したJSONを、単なるスタイルシートにするのではなく、TypeScriptの`const`オブジェクトに変換する。これがパイプラインの心臓部となる。

—

2. 実装の骨子:Penpot API × Custom CLI による自動生成

PenpotのAPIはオープンだ。これを利用し、WebhookやCI/CDパイプライン上で動作するTypeScript製CLIを構築する。

独自CLIのアーキテクチャ概要

1. Fetcher: Penpot APIからDesign TokensをJSONとして取得。
2. Transformer: `zod`を用いてJSONをバリデーションし、Headless UIのPropsスキーマへマッピング。
3. Generator: `ts-morph`を用いて、型定義付きのReactコンポーネントを静的生成。

核心となる変換ロジック(抜粋)

// scripts/sync-tokens.ts
import { Project } from ‘ts-morph’;

/

  • PenpotのトークンをHeadless UIのPropsへ変換する関数
  • @param token Penpot APIから取得したトークン構造

/
function mapToHeadlessProps(token: any) {
return {
variant: token.properties.intent === ‘primary’ ? ‘primary’ : ‘secondary’,
// アクセシビリティを担保するためのaria属性を強制的に付与
‘aria-label’: token.metadata.label,
};
}

// ts-morphでコードを生成する際の戦略
const project = new Project();
const sourceFile = project.createSourceFile(‘generated/Button.tsx’, “”, { overwrite: true });

sourceFile.addImportDeclaration({
namedImports: [‘Button as HeadlessButton’],
moduleSpecifier: ‘@radix-ui/react-button’,
});

// コンポーネント定義の自動注入
sourceFile.addVariableStatement({
declarationKind: ‘const’,
declarations: [{
name: ‘GeneratedButton’,
initializer: `(props) => `
}]
});

—

3. アクセシビリティの強制(Enforcement)

デザインシステムにおいて「アクセシビリティ」は個人のスキルに委ねてはならない。コンポーネントの生成プロセスで、WAI-ARIAの必須属性を型制約として強制するのが我々の流儀だ。

  • 自動lintチェック: 生成されたコンポーネントに対し、`eslint-plugin-jsx-a11y`のルールを動的に適用するCIステージを設ける。
  • Propsの静的検査: Penpotの「説明(Description)」フィールドをAPIで取得し、それを生成コードの`JSDoc`として埋め込む。これにより、開発者がコードを書く際にIDE上でアクセシビリティの注釈がポップアップする。

—

4. パフォーマンスハック:メモリ効率とビルド速度

大規模なプロジェクトでは、数百のコンポーネントをリアルタイムで同期するとビルド時間が肥大化する。

1. Incremental Generation: PenpotのAPIで「変更があったレイヤーID」のみを取得し、差分のみを再生成するキャッシュ戦略を導入せよ。
2. Tree Shakingの最適化: 自動生成するコードには `sideEffects: false` を明示し、ヘッドレスUIのビルドサイズを最小化する。
3. メモリ管理: Node.jsのヒープ不足を防ぐため、`stream`処理でトークンJSONを逐次処理し、巨大なオブジェクトツリーのメモリ占有を避ける。

—

結びに代えて:エンジニアが目指すべき地平

デザインツールは「絵を描く場所」ではない。「プロダクトの仕様を定義するデータベース」であるべきだ。

Penpotを選んだ君たちは、Figmaという閉じたブラックボックスから脱却し、完全な透明性を手に入れた。デザインが更新された瞬間、CLIが走り、テストがパスし、PRが作成される。人間は「どう作るか」ではなく「何を解決するか」に全リソースを投下できる。

これが、我々アーキテクトが辿り着いた「デザイン・エンジニアリングの終着点」だ。

さあ、コードを書け。そして、そのパイプラインをさらに研ぎ澄ませ。完璧なシステムなど存在しない。ただ、毎日昨日の自分より洗練されたシステムを構築し続けるプロセスがあるだけだ。

タイトルとURLをコピーしました