こんにちは。バイタリフィでディレクターをしている横田です。
先日、社内のDify案件で「チャット内にボタンを出して、ユーザーが選択肢をタップで選べるUI」を実装する機会がありました。
クライアントへのデモ用に作ったサンプルアプリなのですが、思いのほかいい感じに仕上がったので、実装方法を記事にまとめてみました。
Difyのチャットフローには、Markdownのボタン機能という地味に良い機能があります。
HTMLの <button> タグを使って、チャット内にクリッカブルなボタンを表示できる機能で、うまく使うとチャット画面がぐっと使いやすくなります。
なかなか情報が少ないので、実案件で得た知見も含めてまとめます。
Difyのチャットフローでボタンが使える?
Difyのチャット画面では、通常ユーザーがテキストを入力してEnterを押すと会話が進みます。ただ、「はい/いいえ」「A案/B案/C案」のような選択肢をテキストで入力させるのはUX的にちょっとしんどい。
そこで使えるのが、Markdownボタン機能です。チャットの回答欄にHTMLの <button> タグを書くと、ボタンとしてレンダリングされ、クリックするとそのボタンに設定した文字列が自動送信される仕組みです。

この機能の何がいいかというと、ワークフロー側の分岐ロジックをLLMの判断でコントロールしながら、UIはボタンで操作できるという点です。特にデモや社内ツールで、「ユーザーに迷わせたくない」「ステップを明確にしたい」というときに重宝します。
基本の書き方:3つの属性を覚えるだけ
ボタンの基本構文はシンプルです。
<button data-message="送信される文字列" data-variant="スタイル">表示テキスト</button>
主要な属性は以下の3つ:
| 属性 | 役割 | 例 |
|---|---|---|
data-message |
クリック時にチャットへ送信される文字列 | "このまま提案書を作成する" |
data-variant |
ボタンの見た目スタイル | "primary" |
data-link |
URLを新しいタブで開く(data-messageより優先) | "https://example.com" |
複数のボタンを横並びにする場合は、ボタンタグの間にスペース1つを入れて並べるだけでOKです(改行不要):
<button data-message="はい" data-variant="primary">✓ はい</button> <button data-message="いいえ" data-variant="secondary">✕ いいえ</button>
💡 ポイント:data-message の文字列がそのままワークフローの分岐キーになります。システムプロンプトでシナリオ分岐を定義するとき、このボタン文字列を分岐条件として使います。
ボタンのスタイル(data-variant)一覧
DifyのGitHubソースコードを確認したところ、data-variant に指定できる値は7種類あります。よく使うのは最初の4つです。
| 値 | 見た目 | 使いどころ |
|---|---|---|
primary |
塗りつぶし・最も目立つ | メインアクション(「作成する」「開始する」) |
secondary |
枠線あり・標準 | サブアクション(「変更したい」「別の方法で」) |
tertiary |
薄め・控えめ | キャンセル・中止など最低優先度のアクション |
warning |
警告色(赤・オレンジ系) | 削除・リセットなど注意が必要なアクション |
ghost |
背景なし・最小限 | 補足的なリンクボタンなど |
secondary-accent |
枠線+アクセントカラーのテキスト | 強調したいサブアクション |
ghost-accent |
背景なし+アクセントカラーテキスト | リンク的な扱いのボタン |
実案件では primary / secondary / tertiary の3種類だけ覚えておけばほぼ事足ります。ユーザーに「押してほしいボタン」はprimary、「あってもいいけど控えめ」はsecondary、「キャンセル」はtertiary、という使い分けが直感的でわかりやすいです。
ボタンを使える場所・使えない場所
Difyのボタン機能を使う前に、どこで動いて、どこでは動かないかを把握しておくことが重要です。
| 場所 | 動作 | 備考 |
|---|---|---|
| チャットフロー(Chatflow)の回答ノード | ✅ 動く | メインの使い場所 |
| チャットフローのLLMノード出力 | ✅ 動く | LLMに生成させる場合はこちら |
| opening_statement(開始メッセージ) | ✅ 動く | 初期ボタンを表示するのに使える |
| 通常のワークフロー(Workflow)の終了ノード | ⚠️ 表示はされる | クリックしてもメッセージ送信されない |
| チャットボット(シンプルチャット) | ✅ 動く | チャットフローと同様 |
⚠️ 注意:通常のワークフロー(Workflow)モードの終了ノードにボタンを書いてもクリックは機能しません。ボタンが動作するのはチャットフロー(Chatflow)モードのみです。
⭐ LLMにボタンを出力させるプロンプト設計のコツ
ボタンを使う方法は大きく2パターンあります。
- 回答ノード(Answerノード)に直書き:ボタンHTMLをテンプレートとして固定で埋め込む
- LLMノードに動的に生成させる:LLMが文脈に応じてボタンを出力する
固定ボタンはシンプルですが、今回のデモアプリのように会話の流れに応じて選択肢が変わるUIを作りたいなら、LLMに生成させるアプローチが有効です。ただしここに大きなハマりポイントがあります。
ハマりポイント:コードブロックに包まれると動かない
LLMに「HTMLボタンを出力してください」と指示すると、モデルによっては以下のようにコードブロック(バッククォート3つ)で包んで出力してしまいます:
```html
<button data-message="はい" data-variant="primary">はい</button>
```
こうなるとボタンとしてレンダリングされず、コードブロックとしてテキスト表示されてしまいます。
解決策:プロンプトで明示的に禁止する
システムプロンプトに以下のような記述を加えると効果的です:
# 重要な出力ルール
1. **応答の最後に、必ずHTMLボタン形式の選択肢を提示すること。**
形式:`<button data-message="送信される文字列" data-variant="スタイル">表示テキスト</button>`
※ HTMLボタンタグは、コードブロック(```)の中に入れないこと。
※ 複数のボタンを並べる場合は、各ボタンの間にスペース1つだけ入れて横並びにすること(改行なし)。
2. HTMLボタンタグは正確に書くこと。data-message属性の値はクリック時に送信される文字列であり、
シナリオの分岐キーになる。
モデル選びも重要
フォーマット遵守の精度はモデルによって大きく差が出ます。GPT-4o / GPT-4.1 / Claude系は指示通りのHTML出力が安定している印象です。軽量モデルを使う場合はテンプレートの固定化や、コード検証ノードの追加を検討してみてください。
実際の活用例:提案書作成デモアプリ
今回作ったのは「提案書作成アシスタント」のデモアプリです。クライアントに本番アプリのUIフローをイメージしていただくためのデモ用ChatFlowで、LLMがロールプレイで進行を制御しながら、HTMLボタンでユーザーの操作を誘導する構成にしています。
opening_statement でファーストボタンを表示
アプリを開いた瞬間から操作を始められるよう、開始メッセージ(opening_statement)にボタンを埋め込みました:
opening_statement: |
🎯 **提案書作成アシスタント (DEMO)**
下記のボタンから操作を開始してください。
<button data-message="企業分析を開始する" data-variant="primary">▶ 企業分析を開始する</button>
ユーザーが「▶ 企業分析を開始する」をクリックすると、「企業分析を開始する」という文字列がチャットに自動送信され、LLMがその文字列を受け取って次のシナリオを展開します。
分析完了後の選択肢ボタン
LLMが企業分析結果を表示した後、次のアクションをボタンで提示します:
<button data-message="このまま提案書を作成する" data-variant="primary">✓ このまま提案書を作成</button> <button data-message="サービスを変更したい" data-variant="secondary">↻ サービスを変更</button> <button data-message="中止する" data-variant="tertiary">✕ 中止する</button>
primary(メインアクション) / secondary(代替案) / tertiary(中止)の3段階で視覚的な優先度をつけているのがポイントです。クライアント様のUXレビューでも「操作の優先度がひと目でわかる」と好評でした。
シナリオ分岐のシステムプロンプト設計
LLMへの指示は、sys.query(ユーザーの直近の入力)に応じてシナリオを切り替える形で設計しています:
ユーザー入力(sys.query)に応じて、以下のシナリオに沿って応答すること。
## 入力が「企業分析を開始する」の場合
→ 企業分析のデモ結果を表示し、次の操作ボタンを出力する。
## 入力が「このまま提案書を作成する」の場合
→ 提案書ドラフトのデモを表示し、出力形式ボタンを提示する。
## 入力が「サービスを変更したい」の場合
→ サービス選択ボタンを提示する。
## それ以外の入力の場合
→ ボタン操作をご案内するメッセージを返す。
ワークフローのIFノードで分岐させるのではなく、LLM1ノードだけでシナリオ全体をロールプレイ的に制御しているのがこのデモのミソです。デモ用途なら構成がシンプルになるので、クライアントへの提案段階ではこの方式が取り回しやすいと感じています。
✅ この構成のメリット:ワークフローのノード数を最小限に抑えられるため、デモ・PoC段階での素早いプロトタイピングに向いています。本番実装では、分岐ロジックをIFノードやコードノードに切り出すことでメンテナンス性が向上します。
ハマりどころ・注意点まとめ
実装中に引っかかったポイントと対策をまとめます。
① LLMがコードブロックでHTMLを包んでしまう
→ 対策:システムプロンプトで「 “`(バッククォート3つ)の中に入れないこと」と明記する。GPT-4.1やClaude系は遵守率が高い。
② suggested_questions と Markdownボタンは別物
Difyの「おすすめの質問(suggested_questions)」は opening_statement の直下にしか表示されず、スタイルも異なります。会話中のステップに応じてボタンを切り替えたい場合は、Markdownボタンを使います。
③ data-link と data-message を同時に設定した場合
両方設定すると data-link が優先され、URLが開きます。data-message(チャットへの送信)は実行されません。意図しない挙動を防ぐため、どちらか一方だけ設定するようにしましょう。
④ 無効な data-variant を指定した場合
スペルミスなど無効な値を指定してもエラーにはならず、デフォルトスタイルで表示されます。意図した色にならないときはまず値の確認を。
⑤ ボタンの横並びが崩れる
ボタン間の区切りは「スペース1つ」が正解。改行(<br>)を入れると縦並びになります。また、ボタンが多すぎると画面幅によっては折り返されるので、1回の提示は3〜4個までが見た目的にもUX的にもベターです。
まとめ
Difyのチャットフローで使えるMarkdownボタン機能、いかがでしたでしょうか。改めてポイントを整理すると:
- data-message でクリック時の送信文字列を、data-variant でスタイルを指定する
- チャットフローのみで動作(通常のワークフローは不可)
- LLMに動的に生成させる場合はコードブロック禁止を明記すること
- ボタンの優先度は primary > secondary > tertiary で視覚的に設計する
- デモ・PoCならLLM1ノードでロールプレイ的に全シナリオ制御するアプローチが素早い
シンプルな機能ですが、うまく使うとチャットアプリのUXが格段に上がります。特にクライアント向けのデモや社内ツールで「テキスト入力させずに操作させたい」というケースにはとても有効なので、ぜひ試してみてください。
バイタリフィではDifyを活用したAIチャットアプリの設計・構築支援も行っています。「自社のフローに組み込みたい」「デモを作って社内稟議を通したい」といったご相談があれば、お気軽にお問い合わせください。
📚 参考リンク
※「Dify」は LangGenius, Inc. の商標です。本記事は同社のブランド使用規約に基づく解説目的の記事であり、同社による公式な発信・推奨ではありません。