こんにちは!プロダクトデザイナー、そしてフロントエンドエンジニアの皆さん。日々、デザインとコードのギャップに頭を悩ませていませんか?
「Figmaは便利だけど、オープンソースの自由度やエンジニアのメンタルモデルとの完全な同期が欲しい」
そう感じているなら、Penpotは間違いなくあなたの最強の相棒になります。
特にPenpotの真骨頂は、SVGベースの純粋なWeb標準(CSS GridやFlexbox)に根ざしたレイアウトエンジンにあります。今回は、そんなPenpotの「コンポーネントバリアント」と「ネスト構造」を極限まで使いこなし、デザインとコードの境界線を溶かす設計手法を、優しく、そして徹底的に解説していきましょう。
これをマスターすれば、デザイナーの意図がそのままエンジニアのコンポーネント設計になり、毎日の修正地獄から解放されますよ。
—
1. Penpotとは何か?(そしてなぜプロトタイピングのゲームチェンジャーなのか)
Penpotは、デザインとコードの世界をシームレスに繋ぐ、オープンソースの協業デザインツールです。従来のツールと何が違うかというと、「概念が完全にCSSと一致している」点にあります。
- FlexboxとCSS Gridのネイティブ実装:余白の取り方やレイアウトの挙動が、そのままCSSになります。
- 完全なオープンソース(Self-host可能):データのロックインがなく、チームのセキュリティポリシーに完全準拠できます。
- SVGネイティブ:コンポーネントの構造が綺麗なSVGコードとして出力され、開発への移行コストが極小です。
導入と初期セットアップ
面倒なインストーラは不要です。今すぐブラウザを開いて始めましょう。
1. [Penpot公式プレビュー(またはセルフホスト環境)](https://penpot.app/)にアクセスし、アカウントを作成します。
2. 新規チーム(Team)を作成し、プロジェクト(Project)を立ち上げます。
3. 最初に行うべき基礎セットアップ:
- カラーパレットのトークン化:`primary/500`, `neutral/100` のように、CSSのカスタムプロパティ(CSS Variables)を意識した命名でカラーを登録します。
- グリッドとタイポグラフィの定義:8ptグリッドシステムを意識したスペーシング(`space-1` = 4px, `space-2` = 8px…)をルール化します。
さあ、環境が整いました。最初の「Hello World」ならぬ、「動的なボタンコンポーネント」を作ってみましょう。
—
2. バリアント機能の限界を超えるネスト構造の活用法
ボタンを設計するとき、あなたはどうしていますか?「Default」「Hover」「Active」「Disabled」、さらに「Primary」「Secondary」「Icon付き」……これらを平面的にバリアント化していくと、組み合わせが爆発(Combinatorial explosion)して破滅します。
ここで登場するのが、「ネスト構造(コンポーネントの中にコンポーネントを入れ子にする手法)」です。
思考のステップ:親と子の分離
- 子コンポーネント(Atom / 内部構造):アイコンやテキストラベルなど、状態を持たない純粋なパーツ。
- 親コンポーネント(Molecule / インタラクション管理):子を内包し、背景色や枠線、パディングの変化といった「状態(State)」を管理するシェル。
> 知見: 全てを1つのコンポーネントのバリアントで解決しようとしてはいけません。「レイアウト構造(サイズ・アイコン有無)」と「インタラクション状態(ホバー・無効)」を直交(Orthogonal)させ、ネストによって合成するのがプロトタイピングの極意です。
—
3. ホバー、フォーカス、ディセーブル状態を効率よく管理する命名規則
エンジニアが実装するときに最もイライラするのは、「このデザインのホバー時はどのカラー変数を使うんだっけ?」と迷う瞬間です。これを解決するのが、CSSの疑似クラスやBEM記法と完全同期した命名規則です。
Penpotのレイヤーパネルとバリアントプロパティでは、以下の規約を徹底してください。
命名規則の黄金ルール
- プロパティ名:`state`, `variant`, `size`
- 値(Value):`default`, `hover`, `focus`, `disabled`
具体的なレイヤー設計例:
1. `Btn/Base` (内部のRowレイアウト。テキストとアイコンを内包)
2. `Component/Button` (親。`variant=primary`, `state=default` などのプロパティを持つ)
Penpotのバリアントパネルで、以下のようにプロパティを設定します。
[Component: Button]
├── Property 1: variant (Values: primary, secondary, ghost)
└── Property 2: state (Values: default, hover, active, disabled)
内部でネストされている `Btn/Base` は常に親のサイズに追従(`Fill container`)させることで、状態が切り替わってもレイアウトが崩れません。
—
4. エンジニアのコーディング規約と完全に一致させるコンポーネント設計
さて、ここからが本題です。デザインされたコンポーネントを、そのままReactやVue、あるいはSvelteのコード構造にマッピングします。
Penpotで設計したネスト構造は、そのままコンポーネントのJSX構造になります。
デザインとコードの対比
Penpot上の構造
- `Button` (親: Flexbox / Container)
- `Icon` (子: Left Icon)
- `Label` (子: Text Node)
実装コード(React + Tailwind CSSの例)
Penpotのプロパティ設計がそのままコードのPropsに直結します。
import React from ‘react’;
// 1. Penpotのプロパティ定義と完全に一致させた型定義
type ButtonVariant = ‘primary’ | ‘secondary’ | ‘ghost’;
type ButtonState = ‘default’ | ‘hover’ | ‘active’ | ‘disabled’;
interface ButtonProps extends React.ButtonHTMLAttributes
variant?: ButtonVariant;
icon?: React.ReactNode;
label: string;
}
export const Button: React.FC
variant = ‘primary’,
icon,
label,
disabled,
…props
}) => {
// 2. Penpotのデザイントークンと同期したスタイルマッピング
const baseStyles = “inline-flex items-center justify-center px-4 py-2 rounded-lg font-medium transition-colors”;
const variantStyles = {
primary: “bg-blue-600 text-white hover:bg-blue-700 active:bg-blue-800 disabled:bg-gray-300”,
secondary: “bg-gray-100 text-gray-800 hover:bg-gray-200 active:bg-gray-300 disabled:bg-gray-50”,
ghost: “bg-transparent text-gray-600 hover:bg-gray-100 active:bg-gray-200”,
};
return (
);
};
この設計がもたらす圧倒的なメリット
1. デザイナーとエンジニアの共通言語ができる:「あ、今のホバー状態は `state=hover` のネストレイヤーの不透明度(Opacity)変えてるだけね」と、会話がコードの文脈で行えるようになります。
2. 仕様変更に強くなる:パディングや角丸を変更したい時、親コンポーネント(Shell)の値を1箇所変えるだけで、全バリアントに伝播します。
3. アクセシビリティの担保:フォーカス状態(Focus state)をコンポーネントのバリアントとして明確に定義しておくことで、キーボードナビゲーションの抜け漏れがデザイン段階で防げます。
—
5. おわりに:デザインツールを「コードの鏡」にしよう
Penpotを使ったコンポーネントバリアントのネスト設計、いかがでしたでしょうか?
最初は「レイヤーを分けるの面倒だな」と感じるかもしれませんが、一度この仕組みをチームに導入すると、デザインの修正地獄から解放され、プロダクトの品質が爆発的に向上します。ツールは単なるお絵描きソフトではありません。「コードベースの設計図」として使いこなしてこそ、真価を発揮します。
さあ、今すぐPenpotを開いて、あなたのコンポーネントを美しくネストさせてみましょう。毎日の作業が、驚くほど軽やかになりますよ!