Claude x Figma によるコンポーネント開発のはじめ方


こんにちは。フロントエンドエンジニアの辻です。
Gaji-Labo では「AI Friendlyな事業支援会社」を目指して、タスクフォースを組成し、日々のタスクを AI で効率化できないか模索しています

本記事の目的

さて、本記事はコンポーネント開発の第一歩として、Claude x Figma によるコンポーネント開発の手法を探っていきます。

手始めとして、あまり深く考えずにプロンプトを組み立てて、AI にコンポーネント作成を依頼すると、どのような結果が出力されるのかを見てみます。AI のお手並み拝見ですね。
個人的にはモノリスティックなモックコンポーネントが出力されるのがゴールラインと考えています。
「多少の不具合は許容しつつ、開発中のアプリのページがある程度は操作できること」が理想的ですね。

使用するツール・モデル

Cline

Cline とは AI アシスタントして開発者を支援するための Visual Studio Code(VSCode)用の拡張機能です。
本記事では Cline ver.3.5.1 を利用します。

公式ドキュメントサイトのWhat Can Cline Do? には、Cline にできることとして下記が列挙されていますね。

  • Edit and create files in your project
  • Run terminal commands
  • Search and analyze your code
  • Help debug and fix issues
  • Automate repetitive tasks
  • Integrate with external tools

Cline 公式サイト:https://cline.bot/
Clie VSCode マーケットプレイス:https://marketplace.visualstudio.com/items?itemName=saoudrizwan.claude-dev

Claude

Claude は、Anthropic 社が開発・提供するAIアシスタントです。
Claude には、いろいろなモデルがありますが、本記事では Claude 3.7 Sonnet を利用します。(本記事執筆時点で最新版のモデル)

Anthropic社 公式サイトのトップに「Claude’s capabilities」として、Claude にできることが紹介されていますね。

  • Advanced reasoning
  • Vision analysis
  • Code generation
  • Multilingual processing

公式サイト:https://www.anthropic.com/claude

コンポーネント生成するまで

下準備

まず下準備として、プロジェクトは素の Next.js v15 を用意しておきます。
そこに npm で bootstrapreact-bootstrap を入れておきました。
さて、ここからは Cline + Claude に任せてみます。

プロンプト

プロンプトは以下の一文のみ。本当にコレだけです。

https://www.figma.com/proto/75Zox6cOneIh13fL4CW00p/Bootstrap-5-UI-Kit-(Community)?node-id=5491-1724&t=ljPtWtinyRmrCgWL-0&scaling=scale-down&content-scaling=fixed&page-id=5491%3A2 
上記のページを元にコンポーネントとページファイルを作成してください。

Figma のプレビューページは、Bootstrap 5 UI Kit を元にして作成しました。

出力結果

上記のプロンプトに対する出力結果が以下の通りです。

出力されたファイルのディレクトリ構成

./
├── README.md
├── app
│   ├── page.tsx … 更新
│   ├── contact
│   │   └── page.tsx … 新規作成
│   └── profile
│       └── page.tsx … 新規作成
├── components
│   ├── navigation
│   │   └── Tabs.tsx … 新規作成
│   ├── pagination
│   │   └── Pagination.tsx … 新規作成
│   └── table
│       └── UserTable.tsx … 新規作成
└── data
    └── users.ts … 新規作成

出力されたファイルの内容

出力内容のすべてを挙げていくとキリがないので、いくつかピックアップしていきます。

/app/page.tsx

'use client';

import React, { useState } from 'react';
import { Container, Card } from 'react-bootstrap';
import Tabs from '../components/navigation/Tabs';
import UserTable from '../components/table/UserTable';
import Pagination from '../components/pagination/Pagination';
import { users } from '../data/users';

export default function Home() {
  const [activeTab, setActiveTab] = useState('home');
  const [currentPage, setCurrentPage] = useState(1);
  const totalPages = 3; // For demonstration purposes

  const handleTabSelect = (key: string | null) => {
    if (key) {
      setActiveTab(key);
      if (key !== 'home') {
        // Navigate to the selected tab
        window.location.href = `/${key}`;
      }
    }
  };

  const handlePageChange = (page: number) => {
    setCurrentPage(page);
  };

  return (
    <Container className="py-4">
      <Card>
        <Card.Header>
          <h4>Home</h4>
        </Card.Header>
        <Card.Body>
          <Tabs activeKey={activeTab} onSelect={handleTabSelect} />
          <UserTable users={users} />
          <Pagination 
            currentPage={currentPage} 
            totalPages={totalPages} 
            onPageChange={handlePageChange} 
          />
        </Card.Body>
      </Card>
    </Container>
  );
}

Home 画面(/)のページファイルと画面キャプチャになります。
画面構成としては、概ね意図通りに出力されていますね。

タブを押下するとそれぞれ Profile 画面(/profile)と Contact 画面(/contact)へ遷移するようにできています。
遷移時の処理は handleTabSelect 関数として記載されていました。愚直な実装ではありますが、ページ遷移自体は問題なく動作します。

ページネーションは置いてあるだけで特に動作はしませんでした。
(とくにプロンプトで言及していませんので、当然と言えば当然ですね。)

/components/navigation/Tabs.tsx

import React from 'react';
import { Nav } from 'react-bootstrap';

interface TabsProps {
  activeKey: string;
  onSelect?: (key: string | null) => void;
}

const Tabs: React.FC<TabsProps> = ({ activeKey, onSelect }) => {
  return (
    <Nav variant="tabs" activeKey={activeKey} onSelect={onSelect} className="mb-3">
      <Nav.Item>
        <Nav.Link eventKey="home">Home</Nav.Link>
      </Nav.Item>
      <Nav.Item>
        <Nav.Link eventKey="profile">Profile</Nav.Link>
      </Nav.Item>
      <Nav.Item>
        <Nav.Link eventKey="contact">Contact</Nav.Link>
      </Nav.Item>
    </Nav>
  );
};

export default Tabs;

上記は Tabs コンポーネントです。
プロンプトでは「コンポーネント分割をして。」などの依頼は一切なかったにも関わらず、適切な粒度でコンポーネントを切り出して各ページファイルに展開していました。

Claude が素の HTML・CSS で独自実装することもなく、ちゃんと react-bootstrap を利用してコンポーネントを作成していますね。

/components/table/UserTable.tsx

import React from 'react';
import { Table } from 'react-bootstrap';

interface User {
  id: number;
  firstName: string;
  lastName: string;
  handle: string;
}

interface UserTableProps {
  users: User[];
}

const UserTable: React.FC<UserTableProps> = ({ users }) => {
  return (
    <Table hover>
      <thead>
        <tr>
          <th>#</th>
          <th>First</th>
          <th>Last</th>
          <th>Handle</th>
        </tr>
      </thead>
      <tbody>
        {users.map((user) => (
          <tr key={user.id}>
            <td>{user.id}</td>
            <td>{user.firstName}</td>
            <td>{user.lastName}</td>
            <td>{user.handle}</td>
          </tr>
        ))}
      </tbody>
    </Table>
  );
};

export default UserTable;
export interface User {
  id: number;
  firstName: string;
  lastName: string;
  handle: string;
}

export const users: User[] = [
  {
    id: 1,
    firstName: 'Mark',
    lastName: 'Otto',
    handle: '@mdo'
  },
  {
    id: 2,
    firstName: 'Jacob',
    lastName: 'Thornton',
    handle: '@fat'
  },
  {
    id: 3,
    firstName: 'Larry',
    lastName: 'the Bird',
    handle: '@twitter'
  }
];

上記は、UserTable コンポーネントと、Table に流し込むダミーデータです。
ダミーデータ出力についても、プロンプトでは一切言及しませんでしたが、Claude がよしなにやってくれました。

/app/contact/page.tsx

'use client';

import React, { useState } from 'react';
import { Container, Card, Form, Button } from 'react-bootstrap';
import Tabs from '../../components/navigation/Tabs';

export default function Contact() {
  const [activeTab, setActiveTab] = useState('contact');

  const handleTabSelect = (key: string | null) => {
    if (key) {
      setActiveTab(key);
      if (key !== 'contact') {
        // Navigate to the selected tab
        window.location.href = key === 'home' ? '/' : `/${key}`;
      }
    }
  };

  return (
    <Container className="py-4">
      <Card>
        <Card.Header>
          <h4>Contact</h4>
        </Card.Header>
        <Card.Body>
          <Tabs activeKey={activeTab} onSelect={handleTabSelect} />
          <div className="mt-4">
            <h5>Contact Us</h5>
            <Form>
              <Form.Group className="mb-3" controlId="formName">
                <Form.Label>Name</Form.Label>
                <Form.Control type="text" placeholder="Enter your name" />
              </Form.Group>

              <Form.Group className="mb-3" controlId="formEmail">
                <Form.Label>Email address</Form.Label>
                <Form.Control type="email" placeholder="Enter your email" />
                <Form.Text className="text-muted">
                  We'll never share your email with anyone else.
                </Form.Text>
              </Form.Group>

              <Form.Group className="mb-3" controlId="formMessage">
                <Form.Label>Message</Form.Label>
                <Form.Control as="textarea" rows={3} placeholder="Enter your message" />
              </Form.Group>

              <Button variant="primary" type="submit">
                Submit
              </Button>
            </Form>
          </div>
        </Card.Body>
      </Card>
    </Container>
  );
}

Home 画面(/)以外にも作成してもらったページがあるので、見ていきましょう。
上記は、Contact 画面(/contact) のページファイルと画面キャプチャです。

Claude に依頼したのは Figma のプレビューページの 1 ページのみであり、フォームのデザインはありませんでした。
…にも関わらず、Claude がよしなにページを作成してくれました。

よしなに作ってもらったページなので「裏側はハリボテかな?」と思いましたが、案外そうでもなく、<From> コンポーネントをはじめ、<Form.Label><Form.Control> コンポーネントなどを利用しています。
ちゃんと react-bootstrap を使っていますし、スタイリングに崩れもありません。
いかにも”それっぽい”フォームが仕上がっています。

フォームの制御機構は、特には実装されていなかったです。
(指示をしていないので、当たり前といえば当たり前ですね。)

所感

さて、本記事の目的は「あまり深く考えずにプロンプトを組み立てて、コンポーネント作成を依頼した場合に、どのような結果が出力されるのか」を確認することにありました。

結論としては、想像以上に良い出力だなと感じています。
たった一文だけでモックを出力をしてくれたことに驚愕しています。

個人的に、いくつかのランタイムエラーやコンパイルエラーが発生して、都度修正する必要があるかな?と思っていました。
例えば、JSX のイテレータにおける key を抜かしてしまう。とか人間がやりがちなミスとかですね。
そういったエラーも一切なかったのもポイントが高かったです。

たった一文だけのプロンプトでしたが、もっと精密なプロンプトを書けば、かなり高精度の出力をしてくれそうで、大きな期待感が持てました。

最近はさまざまな AI ツールが登場していますので、ぜひ Claude 以外のツールも触って業務効率化を測れないかを探っていってみます!

Gaji-Labo フロントエンドエンジニア向けご案内資料

Gaji-Laboでは、React経験が豊富なフロントエンドエンジニアを募集しています

弊社ではReactの知見で事業作りに貢献したいフロントエンドエンジニアを募集しています。大きな制作会社や事業会社とはひと味もふた味も違うGaji-Laboを味わいに来ませんか?

もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!

求人応募してみる!

投稿者 Tsuji Atsuhiro

フロントエンドエンジニア。 DTP・Webデザイナーを経験した後、フロントエンドエンジニアに転向。HTML/CSS/JavaScriptを中心にWeb開発を担当してきました。 UI・UXに興味があり、デザイン・コーディング両面から考えられるデザインエンジニアを目指しています。 普段はマラソンやボクシングなどで体を動かしてます。