PlantUMLの表現力をTailwind CSSで設計書品質に仕上げる

テキストベースの図、もっと表現力が欲しい

ども!PlantUML × VS Codeの記事を書いた龍ちゃんです。

Claude Codeを使って爆速でPlantUMLを書けるようになりました。でも!実際に設計書やブログに貼るとなると「もうちょっと見た目をなんとかしたい」ってなるんですよね。図の横に設計ポイントを添えたり、色を揃えて見やすくしたり。

そこで今回は、PlantUMLの図にTailwind CSSの装飾を組み合わせます。1つのHTMLファイルにPlantUML図 + 注釈パネルをまとめて、PNGに変換する仕組みです。

「なんでPlantUML?」理由はシンプルで、Mermaidではカバーできない図があるからです。

Mermaidはフロー図やシーケンス図が得意ですけど、この3つは守備範囲外なんですよね。PlantUMLならテキストベースのまま作れます。

Mermaid × Tailwind CSSの組み合わせも同時に記事にしてます。フロー図やシーケンス図を作りたい人は「Mermaid × Tailwind CSS — ハイブリッド図解の作り方」をどうぞ。

今回の内容です。

  • コピペ用テンプレート2種(右サイド注釈型 / 左右均等型)
  • PNG変換の手段3つ

関連記事: 仕様書の図にソースコードと設計意図を添える方法については「仕様書の図はAIに読ませるな — 軽量コードを添える設計パターン」で詳しく書いてます。AIに図の構造を正確に伝えたい人はこちらもどうぞ。

テンプレートA: 右サイド注釈型

コピペして使えるHTMLテンプレートを2種類用意しました。

仕組みはシンプルで、plantuml-encoderというライブラリでPlantUML構文をエンコードしてplantuml.comに投げるだけです。Tailwind CDNとの共存も問題なし。テンプレートをコピペすればそのまま動きます。plantuml.comへの通信が発生するのでネット接続は必要ですが、大抵の開発環境では問題にならないです。

まずはテンプレートA。左にPlantUMLで描画したコンポーネント図、右にTailwind CSSで装飾した注釈パネルを並べるレイアウトです。ドラム型のDB記号やキュー記号が見えるのがPlantUMLならではのポイントですね。

向いてる場面: 横に広がる図(コンポーネント図、シーケンス図)に設計ポイントを3-4個添えたいとき

キャンバスサイズ: 1280x720px

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>コンポーネント図: 3層アーキテクチャ</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://cdn.jsdelivr.net/npm/plantuml-encoder@1.4.0/dist/plantuml-encoder.min.js"></script>
    <style>
        body {
            width: 1280px;
            height: 720px;
            margin: 0;
            padding: 0;
            background: white;
            overflow: hidden;
        }
    </style>
</head>
<body class="p-8">
    <div class="h-full flex flex-col">
        <!-- タイトル -->
        <h1 class="text-2xl font-bold mb-6 text-gray-800">コンポーネント図: 3層アーキテクチャ</h1>

        <!-- メインコンテンツ: 2カラム -->
        <div class="flex gap-6 flex-1">
            <!-- 左: PlantUML図 -->
            <div class="flex-1 flex flex-col">
                <div class="bg-blue-50 border-2 border-blue-300 rounded-lg p-4 flex-1 flex items-center justify-center">
                    <div id="diagram-container" class="flex items-center justify-center">
                        <p class="text-gray-500">Loading diagram...</p>
                    </div>
                </div>
            </div>

            <!-- 右: 注釈パネル -->
            <div class="w-72 flex flex-col gap-4">
                <div class="bg-yellow-50 border border-yellow-300 rounded-lg p-4">
                    <h3 class="font-bold text-yellow-800 mb-2">フロントエンド層</h3>
                    <p class="text-sm text-yellow-900">Web・モバイルの2系統。APIゲートウェイを経由して統一的にアクセス</p>
                </div>
                <div class="bg-blue-50 border border-blue-300 rounded-lg p-4">
                    <h3 class="font-bold text-blue-800 mb-2">バックエンド層</h3>
                    <p class="text-sm text-blue-900">認証・記事・通知をマイクロサービスに分離。ゲートウェイでルーティング</p>
                </div>
                <div class="bg-green-50 border border-green-300 rounded-lg p-4">
                    <h3 class="font-bold text-green-800 mb-2">データ層</h3>
                    <p class="text-sm text-green-900">PostgreSQL + Redis + メッセージキューの3点セット。非同期処理はキュー経由</p>
                </div>
            </div>
        </div>
    </div>

    <script>
        window.addEventListener('DOMContentLoaded', function() {
            const umlSource = `@startuml
skinparam style strictuml
skinparam backgroundColor transparent
package "フロントエンド" {
  [Webアプリ] as Web
  [モバイルアプリ] as Mobile
}

package "バックエンド" {
  [APIゲートウェイ] as GW
  [認証サービス] as Auth
  [記事サービス] as Article
  [通知サービス] as Notify
}

package "データ層" {
  database "PostgreSQL" as DB
  database "Redis" as Cache
  queue "メッセージキュー" as MQ
}

Web --> GW
Mobile --> GW
GW --> Auth
GW --> Article
Article --> DB
Article --> Cache
Article --> MQ
MQ --> Notify
Auth --> DB
@enduml`;

            try {
                const encoded = plantumlEncoder.encode(umlSource);
                const imgUrl = `https://www.plantuml.com/plantuml/svg/${encoded}`;
                const container = document.getElementById('diagram-container');
                container.innerHTML = `<img src="${imgUrl}" alt="コンポーネント図" class="max-w-full max-h-full" />`;
            } catch (error) {
                console.error('Error encoding PlantUML:', error);
                document.getElementById('diagram-container').innerHTML =
                    `<p class="text-red-500">Failed to load diagram: ${error.message}</p>`;
            }
        });
    </script>
</body>
</html>

差し替えは3箇所だけです。

  1. PlantUML構文const umlSource の中身)を自分の図に差し替える
  2. 注釈パネルの内容(右側の3つのパネル)を自分の設計ポイントに差し替える
  3. タイトルを差し替える

注釈パネルは増減しても大丈夫です。3個が収まりいいですけど、2個でも4個でもレイアウトは崩れません。

skinparamについて

Mermaid編では themeVariables で図の配色をTailwindのカラーパレットに合わせましたね。PlantUMLでは skinparam が同じ役割です。

skinparam style strictuml
skinparam backgroundColor transparent

backgroundColor transparent で背景を透明にするだけで、Tailwind側の bg-blue-50 と自然に馴染みます。追加で色をカスタマイズしたい場合はこんな設定もあります。

skinparam packageBackgroundColor #f0f9ff
skinparam ArrowColor #3b82f6
skinparam ParticipantBackgroundColor #dbeafe

Mermaidの themeVariables ほど細かい制御はできないですけど、背景透明にするだけで大抵は十分です。もっと細かく調整したい人はPlantUML公式のskinparamリファレンスを参照してください。

PlantUML固有の記法

テンプレートのコードで使ってる記法と、他の図で使える記法をまとめておきます。

テンプレートAで使用:

記法表示
database "名前"ドラム型DB記号
queue "名前"キュー型記号
package "名前" { ... }パッケージでグループ化

他の図で使える記法:

記法用途
== フェーズ名 ==シーケンス図のフェーズ分割
alt / else / end条件分岐ブロック
ref over A, B外部参照
left to right directionユースケース図の横向きレイアウト

テンプレートB: 左右均等型

テンプレートAだと、縦長の図のときに左側のスペースが余ります。アクティビティ図やユースケース図は縦に伸びるんで、横幅を半々にして右側に説明を置くほうが収まりがいいです。

向いてる場面: 縦長の図(アクティビティ図、ユースケース図)に説明を横に並べたいとき

キャンバスサイズ: 1280x720px

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>アクティビティ図: ユーザー登録フロー</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://cdn.jsdelivr.net/npm/plantuml-encoder@1.4.0/dist/plantuml-encoder.min.js"></script>
    <style>
        body {
            width: 1280px;
            height: 720px;
            margin: 0;
            padding: 0;
            background-color: white;
            overflow: hidden;
        }
    </style>
</head>
<body class="flex">
    <!-- 左: PlantUML図 -->
    <div class="w-1/2 flex items-center justify-center p-8">
        <div id="diagram-container"></div>
    </div>

    <!-- 右: 注釈パネル -->
    <div class="w-1/2 flex flex-col justify-center p-8 space-y-6">
        <h1 class="text-3xl font-bold text-gray-800 mb-4">ユーザー登録フロー</h1>

        <div class="bg-yellow-100 border-l-4 border-yellow-500 p-4">
            <p class="text-gray-800 font-semibold">バリデーション</p>
            <p class="text-sm text-gray-600 mt-1">フォーム入力後、サーバー側でバリデーション。失敗時はエラーメッセージを表示してフォームに戻す</p>
        </div>

        <div class="bg-blue-100 border-l-4 border-blue-500 p-4">
            <p class="text-gray-800 font-semibold">DB保存とリトライ</p>
            <p class="text-sm text-gray-600 mt-1">保存失敗時はエラーログを記録してリトライ画面を表示。ユーザーに再試行の機会を与える</p>
        </div>

        <div class="bg-green-100 border-l-4 border-green-500 p-4">
            <p class="text-gray-800 font-semibold">完了通知</p>
            <p class="text-sm text-gray-600 mt-1">保存成功時は完了メールを送信してから完了画面を表示。メール送信は非同期でもOK</p>
        </div>
    </div>

    <script>
        window.addEventListener('DOMContentLoaded', () => {
            const plantUmlSource = `@startuml
start
:ユーザーがフォームを入力;
if (バリデーション?) then (成功)
  :データベースに保存;
  if (保存成功?) then (はい)
    :完了メールを送信;
    :完了画面を表示;
  else (いいえ)
    :エラーログを記録;
    :リトライ画面を表示;
  endif
else (失敗)
  :エラーメッセージを表示;
  :フォームに戻る;
endif
stop
@enduml`;

            const encoded = plantumlEncoder.encode(plantUmlSource);
            const img = document.createElement('img');
            img.src = `https://www.plantuml.com/plantuml/svg/${encoded}`;
            img.alt = 'アクティビティ図: ユーザー登録フロー';
            img.className = 'max-w-full max-h-full';
            document.getElementById('diagram-container').appendChild(img);
        });
    </script>
</body>
</html>

差し替えポイントはテンプレートAと同じ3箇所です。PlantUML構文、注釈パネル、タイトル。実例はアクティビティ図ですけど、ユースケース図を作りたい場合も const plantUmlSource の中身を差し替えるだけです。

テンプレートAとの違いは2つです。

  1. レイアウトが左右均等w-1/2 + w-1/2)。図と注釈が50:50で並ぶんで、縦長の図に余裕ができます
  2. 注釈のスタイルが border-l-4(左ボーダーアクセント)。テンプレートAのカード型とは雰囲気が変わります

Mermaid編のテンプレートBは縦拡張(1280x1080px)でしたけど、PlantUML編は標準の1280x720pxです。PlantUMLはサーバー側で描画するんで、Mermaidの自動縮小問題がない。CSS transformも不要です。

テンプレート使い分け

条件テンプレート
横に広がる図(コンポーネント図、シーケンス図)A: 右サイド注釈型
縦長の図(アクティビティ図、ユースケース図)B: 左右均等型
注釈が少ない(2-3個)A
注釈にテキストが多いB

迷ったらテンプレートAで試して、図が窮屈ならBに切り替えてください。

PNG変換 — 3つの手段

手段は3つあります。好みと環境に合わせて選んでください。

手段自動化セットアップ向いてる用途
CLIツール(html-screenshot)できるPython + Playwright量産やCI/CD
Playwright MCPできるMCP設定Claude Code連携
ブラウザで直接開く手動なし手軽に確認

CLIツール

uv run html-screenshot --file template-a.html --output template-a.png

以前の記事で紹介したhtml-screenshotツールがそのまま使えます。Playwrightの wait_until="networkidle" がplantuml.comからのSVG取得完了を待ってくれるんで、PlantUML図の描画が終わってからスクリーンショットを撮ってくれます。

Playwright MCP

Claude CodeからPlaywrightのブラウザ操作を直接呼び出す方法もあります。HTMLファイルを開いてスクリーンショットを撮る操作がMCPのツールで完結します。Playwright MCPの記事で詳しく書いてるんで、Claude Code使ってる人はこっちが楽です。

ブラウザで直接開く

一番手軽なのはHTMLファイルをブラウザで直接開くことですね。Tailwind CDNとplantuml-encoder CDNが読み込まれて、PlantUML図がそのまま描画されます。PNG変換は手動(DevToolsやOS標準のスクリーンショット)になりますけど、「ちょっと確認したい」くらいならこれで十分です。

まとめ

Mermaid編のテンプレート2種 + 今回のPlantUML編テンプレート2種で、計4種類が揃いました。

テンプレート図の描画向いてる図
Mermaid A: 右サイド注釈型Mermaidフロー、シーケンス、状態遷移
Mermaid B: 下部グリッド注釈型Mermaid複雑なシーケンス
PlantUML A: 右サイド注釈型PlantUMLコンポーネント、シーケンス
PlantUML B: 左右均等型PlantUMLアクティビティ、ユースケース

フロー図、シーケンス図、状態遷移図に加えて、アクティビティ図、ユースケース図、コンポーネント図もカバーできるようになりました。特にコンポーネント図の database でドラム型DBアイコンが出せるのは、アーキテクチャ図を書く人にはかなり便利です。

おまけとして、Mermaid編でも触れましたがこのテンプレートで作った図はAI連携にも使えます。PlantUMLもMermaidと同じく宣言的記法なんで、AIが構造を正確に読み取れます。注釈パネルに設計意図を書いておけば、人間が見ても、AIが読んでもどっちにも伝わる。この辺の話は仕様書の図にソースコードを添える設計パターンで詳しく書いてます。

もう1つ。このテンプレートを毎回コピペするのも手ですけど、Claude CodeのSKILL機能にreference(参考例)として登録しておくと、「シーケンス図作って」と言うだけでテンプレートに沿った図が出てきます。テンプレートのクオリティがそのままSKILLのクオリティになるんで、良いテンプレートを貯める = 図解の品質を底上げする、という流れができます。コピペ用テンプレートとSKILL化、好きなほうで使ってください。

ほなまた〜

関連ブログ

今回の記事はシリーズの一部です。

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。
エンジニア募集中!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です