Claude Codeを使うようになって、コードを書くスピードは劇的に上がりました。 しかしながら、デザインまわりに目を向けると、 Figmaとコードの二重管理は何も変わっていない ことに気づきました😢 むしろコードが速くなった分、デザイン工程のボトルネックが目立ってきています。 (自分もいまだにFigmaは手で直してます...) 今回は、 「コードはAIで爆速なのに、デザインは手作業のまま」という現象がなぜ起きるのか コードを正本にしたデザイン運用でどう解決できそうか の方法論を整理してみようと思います。 つらみ①:Figmaとの二重管理がきつい 正直、これはみなさん薄々感じていると思うんですが、 「Figmaを更新してもコードは変わらないし、実装が進むほどFigmaは実物から乖離していく...」 ってことよくありませんか? ↓イメージ(Figmaとコードが乖離していく) ロジックが入り実装都合の微調整が入り、気がつくと Figmaはもう最新ではない... それでもデザインは継ぎ足されていって、どんどん乖離していきます。 つらみ②:AIでコードは書けるのに、デザインはいけないのか じゃあデザインもAIでやればいいのでは?と思って試すと、これが意外とうまくいかないんですよね。 Figma Make :生成はできるけど、成果物がその場限りで資産として残らない Figma AI Agent :既存ノードの整理には向いているものの、0→1の生成には向いておらず、精度も現状イマイチ... 結局いろいろ触ってみた結論としては、 コードから生成するパターンが一番精度が高い 、ということでした。 AIが一番得意なのはコードなので、冷静に考えると当たり前ではありますね。 つらみ③:人間による仕様検討漏れ もう1つ、地味だけど致命的なのがこれです。 👨💻エンジニアA「ブランク(0件)状態の表示、考えました?」 👨💻エンジニアB 「バリデーションのエラーメッセージ、全パターン網羅してます?』 →🧑🎨デザイナー 「全部作ります...」 どちらの立場も経験してますがかなり骨が折れる作業ですね😭 バリデーションメッセージって1フォームで十数種類あったりして、それ×画面数をデザイナーが毎回検討して描くのは、正直やってられません... ⇩これはサンプルですが、実プロジェクではもっと複雑になりがちですね.... これ、コード駆動でデータとルールから担保すれば、 **検討漏れ自体を仕組みで防げる** はずなんですよね。 ただし、AIで全部出せるわけではない ここが今回の大事な前提です。 8割 :汎用的な画面 → AIで出せる 2割 :ちゃんとデザインすべき画面 → 人間がやる 2割というのは、例えば求人検索画面のような 検索UIと一覧UIが同居して、条件も多く、シビアな配置が求められる画面 ブランディングに関わる画面・LP ここを0→1でAIにポン出しさせるのは、現状難しいと思っています。 なので今回のスコープは、 「汎用的な8割をAIでサクッと作り、いい感じに保守し、漏れなく検討する。大事な2割はFigmaでデザイナーが作る」 です。「全部コードで作ろう」という話ではありません。 方法論:コードを正にする3つのポイント じゃあどうやるのか。ポイントは3つに整理できました。 要件・API定義をいい感じにやる デザインシステムとデザインルールを決めておく Storybookで見る ① 要件・API定義 汎用的なUIは、 APIが分かれば、必要な画面は大体分かります 。 スキーマとバリデーションが決まっていれば、フォームの構成もエラーメッセージも大体導出できてしまうんですよね。なので、バックエンドとAPI仕様を先に取り決めてからデザインに入るのが、汎用画面では一番楽です。 schema-example.ts /** * WEB履歴書「① プロフィール」セクションだけを切り出した zod スキーマ例。 * * 対応ワイヤーフレーム: web-resume-wireframe.md 169–189 行(編集画面) * 本番相当: src/schemas/web-resume.ts の profileSchema * * 社内記事用。バリデーションは要点だけに絞っている。 */ import { z } from "zod" // --- 共通パターン ----------------------------------------------------------- /** プルダウン選択肢(code + 表示ラベル) */ const coded = z.object({ code: z.string(), label: z.string(),}) // --- ① プロフィール --------------------------------------------------------- /** 最終学歴カード(「+ その他の学歴を追加する」で複数可。最低1件必須) */ const educationSchema = z.object({ startYear: z.number().int().min(1900), endYear: z.number().int().min(1900), status: z.enum(["graduated", "expected", "enrolled", "withdrawn"]), schoolType: z.string().min(1), // 例: 大学 schoolName: z.string().min(1), // 例: サンプル大学 facultyType: z.string().optional(), // 例: 芸術系 facultyName: z.string().optional(), // 例: 工学部… })export const profileSchema = z .object({ // 氏名 [必須] [■] 姓 / 名 name: z.object({ last: z.string().min(1), first: z.string().min(1), }), // 氏名(カナ)[必須] [■] セイ / メイ nameKana: z.object({ last: z.string().min(1), first: z.string().min(1), }), // 生年月日 [必須] [■] 年 / 月 / 日 birthDate: z.object({ year: z.number().int(), month: z.number().int().min(1).max(12), day: z.number().int().min(1).max(31), }), // 性別(任意) RadioGroup: 男性 / 女性 gender: z.enum(["male", "female"]).nullable(), // 現住所 [必須] [■] 〒 / 都道府県 / 市区町村 / 以降の住所 address: z.object({ postalCode: z.string().regex(/^\d{3}-?\d{4}$/), prefecture: z.string().min(1), city: z.string().min(1), street: z.string().min(1), }), // 電話番号 [必須] [■] 固定 / 携帯 — どちらか一方必須 phone: z .object({ landline: z.string().regex(/^\d{10,11}$/).nullable(), mobile: z.string().regex(/^\d{10,11}$/).nullable(), }) .refine((v) => v.landline || v.mobile, { message: "固定電話・携帯電話のどちらか一つを必ず入力してください。", }), // 最終学歴 [必須] educations: z.array(educationSchema).min(1), // 現在の就業状況 [必須] employmentStatus: z.string().min(1), // 現在(直近)の年収(任意) currentIncome: coded.nullable(), // 配偶者(任意) true=有り / false=無し spouse: z.boolean(), })export type Profile = z.infer<typeof profileSchema> mock-api-example.ts /** * WEB履歴書「① プロフィール」セクションだけを切り出したモック例。 * * 対応ワイヤーフレーム: web-resume-wireframe.md 169–189 行(編集画面) * 本番相当: src/mocks/web-resume.ts の MOCK_RESUME.profile * * 社内記事用。プルダウン選択肢も代表値だけ載せている。 */ import type { Profile } from "./schema-example" // --- プルダウン選択肢(記事用に最小限) ------------------------------------ export const PREFECTURES = ["北海道", "東京都", "神奈川県", "大阪府", "福岡県"]export const EMPLOYMENT_STATUSES = ["正社員", "契約社員", "派遣社員", "離職中"]export const INCOME_RANGES = [ { code: "500-599", label: "500〜599万円" }, { code: "600-649", label: "600〜649万円" }, { code: "650-799", label: "650〜799万円" },]export const EDUCATION_STATUSES = [ { code: "graduated", label: "卒業・修了" }, { code: "expected", label: "卒業見込み" }, { code: "enrolled", label: "在学中" },]export const SCHOOL_TYPES = ["大学", "大学院", "短期大学", "専門学校", "高等学校"]export const SPOUSE_OPTIONS = [ { value: false, label: "無し" }, { value: true, label: "有り" },] // --- モックデータ ------------------------------------------------------------ /** * 編集画面ワイヤーフレームに載っている値をそのまま入れた例。 * * ``` * │ 氏名 [必須] │ [■] 姓 [三上____] 名 [宗祐____] │ * │ 生年月日 [必須] │ [■] [2001] 年 [3] 月 [23] 日 │ * │ 現住所 [必須] │ [■] 〒 [180-0002] [東京都] [三鷹市] │ * │ 電話番号 [必須] │ [■] [0700000000…] (携帯電話) │ * │ 最終学歴 [必須] │ 2019〜2025 卒業・修了 / 大学 サンプル大学 │ * │ 就業状況 [必須] │ 正社員 / 600〜649万円 / 配偶者: 無し │ * ``` */ export const MOCK_PROFILE: Profile = { name: { last: "三上", first: "宗祐" }, nameKana: { last: "ミカミ", first: "ソウスケ" }, birthDate: { year: 2001, month: 3, day: 23 }, gender: "male", address: { postalCode: "180-0002", prefecture: "東京都", city: "三鷹市", street: "適当な住所-1-2-3 サンプルマンション101", }, phone: { landline: null, mobile: "07000000000" }, educations: [ { startYear: 2019, endYear: 2025, status: "graduated", schoolType: "大学", schoolName: "サンプル大学", facultyType: "芸術系", facultyName: "工学部システムデザイン学科", }, ], employmentStatus: "正社員", currentIncome: { code: "600-649", label: "600〜649万円" }, spouse: false,} レイアウトだけは決めておく必要があるので、そこは テキストベースのワイヤーフレーム をAIと壁打ちします。四角と線の雑なものでも、イメージの担保には十分でした。 wireframe-example.md ┌════ ① プロフィール ═══════════════════════════════════┐│ 氏名 [必須] │ [■] 姓 [三上____] 名 [宗祐____] ││ 氏名(カナ)[必須]│ [■] セイ [____ _] メイ [_____ ] ││ 生年月日 [必須] │ [■] [2001] 年 [3] 月 [23] 日 ││ 性別 │ ( )男性 ( )女性 ← RadioGroup ││ 現住所 [必須] │ [■] 〒 [180-0002] ││ │ [東京都 ▾] [三鷹市__ _] (市区町村) ││ │ [■] [適当な住所-…_ ____ _] (以降の住所)││ 電話番号 [必須] │ [■] [_ ______ _] (固定電話) ││ │ [■] [0700000000…] (携帯電話) ││ │ どちらか一つを必ず入力してください。… ││ 最終学歴 [必須] │ ┌ 学歴カード ───────────────────────┐ ││ │ │[2019▾] 年〜 [2025▾] 年 [卒業・修了▾]│ ││ │ │[大学 ▾] [サンプル大学__________] │ ││ │ │[芸術系▾] [工学部…] │ ││ │ └────────────────────────────────────┘ ││ │ [ + その他の学歴を追加する ] ││ 現在の就業状況[必須]│ [正社員 ▾] ││ 現在(直近)の年収│ [600〜649万円 ▾] ││ 配偶者 │ [無し ▾] │└═══════════════════════════════════┘ API仕様から仕様を詰める おのずとUIが決まる レイアウトだけテキストワイヤーで詰める この3ステップで、汎用画面はほぼ解決すると思っています。 ② デザインシステムとデザインルール デザインシステムは言わずもがなですね。車輪の再発明がなくなり、コード品質が安定します。 加えて、コンポーネントの配置や余白のルールといったガイドラインをある程度決めておく必要があります。ここが曖昧だと、AIが毎回違う判断をしてくるので... ※デザインシステムの作り方自体は今回のスコープ外なので、他社事例を見ながら別途詰める必要があります。こちらはまたの機会に触れられればと思います👋 ③ Storybook デザイナー観点で一番大きいのは、 アプリを起動せずにUIが見られる ことです。 エラー時、ホバー時、データが入った時...といった状態は、本来ならデータ準備・わざとエラー発生・ロジックの先行実装が必要でした。Storybookなら、全状態がカタログとして一目で並びます。 ↓イメージ(Default / 0件 / 読込中 / エラー が並ぶ状態カタログ) これを支えるのが Container / Presentationalパターン です。UI(見た目)とロジックを分離して、見た目だけにフォーカスできるようにする。この分離ルールを守ることで、「それほぼフロントエンドエンジニアの仕事では?」とならずに、「デザインシステムの枠を作る」スコープに収まります。 ルール統制やディレクトリ構成はフロントエンドエンジニアとの協業が必須になるポイントです。 他社でもガリガリ使われているので、再現性は高そう ちなみにこれ、机上の空論ではなくて、他社では半分ほどAIメインでデザインが回っている事例が既に出てきています そろそろうちもやったほうがいいんじゃないか、という気持ちです。 Figmaを使わずStorybookでデザイン描いたら検討漏れが131件見つかった Storybookでデザイナーが仕様を書く——エンジニアがやったこと・やらなかったこと 知り合いからも、社内半分のプロジェクトは、Figmaレスで、デザイナーがClaudeCodeを触っているみたいなことも聞いてたりします 社内ループへの落とし込み 方法論はなんとなくわかったところで、一番難しいところが、 「既存のワークフローにどう組み込むのか」 というところです。 前提:Figmaは取り除けない 最初に言っておくと、 Figmaのループは取り除けない と思っています。 【理由①】 2割の画面はデザイナーが作るわけですが、 作り勝手でFigmaにかなうものが今のところない こと。 【理由②】 Storybookやモック環境は基本的に「1画面ずつ」しか見られません。画面を横にペタペタ並べて、フロー図や画面間の比較を1画面でやる、あの体験はFigmaでないと提供できないんですよね。 なのでFigmaは前提として残しつつ、その上でどう組み込むかを考えます。 大原則:コードを本体にする Figmaのデザインは乖離していくもの、という前提に立つと、 最終的な正本はコード(Storybook)に置く のが一番のポイントです。 Fixした画面はFigmaに「輸入」します。ページだけでなく、コンポーネントやファンデーションもです。デザイナーは輸入された最新のFigmaライブラリの上で、次の2割のデザインを進める、という流れです。 双方向連携の手段 方向 手段 コード → Figma story.to.design。エラーハンドリング等の状態込みで、StorybookからFigmaコンポーネントとして楽にインポートできる( https://story.to.design/ ) Figma → コード Figma MCPで読み込む 双方向を連携させることで、Figmaでの微調整体験は残しつつ、汎用画面はコード側で量産できます。結果として、仕様の抜け漏れも減っていくフローになります。 ↓イメージ(コード⇔Figmaの双方向ループ図) 大事なのは、同期の 正本はあくまでコード であり続けることです。ここが崩れると、また二重管理に戻ります。 0→1は楽だけど、保守運用が問題 これ結構罠だと思っているんですが、 AIの特性として 0→1で作るのは楽 なんですよね... 問題はその後の改修・拡張です。 作って終わりではなく、ルールファイル・状態カタログ・Figma同期といった仕組みで、 改修ループにAIが入り続けられる状態 を作ることが、実は一番大事なポイントだと思っています。 デザイナーはどう入るのか 前提として、デザイナーがコードを書けるなら、フロントエンドエンジニアとしてバイブコーディングしてしまうのが全体としては一番楽です。 書けない場合はこうです。 デザイナーの成果物は「デザインブランチ」です。エンジニアはそれを下敷きにロジックをくっつける。デザイナーがコードを書く必要はなく、入力は自然言語の指示だけになります。 まとめ 今回大事なところは、2点です。 ① 正本をコードに置き、Figmaはコードから生成する Figmaとコードの二重管理問題は、ツールの問題ではなくワークフロー設計の問題でした。 コードを正本にして、story.to.design × Figma MCPで双方向同期すれば、Figmaの体験を残したまま保守を自動化できます。 ② 8割/2割の分業を設計する 全部AIでも、全部人間でもなく、汎用的な8割をコード+AIで量産し、シビアな2割にデザイナーの時間を集中させる。この分業設計こそが、デザインコストと検討漏れを同時に減らす鍵だと思っています。 この辺りは、実際に社内のプロジェクトに入れてみようと思っているので、結果はまた別の記事で共有できればと思います。 最後まで見ていただきありがとうございました!