widget-design について
widget-designは、GPT AppsとMCP Apps向けの会話内ウィジェットを作る開発者向けデザインスキルです。コンパクトでサンドボックス化された表示に適した設定、レイアウト、操作、状態管理の指針をまとめています。
主な機能
- 01GPT AppsとMCP Appsの設定指針
- 02Reactとテンプレート文字列の選択
- 03コンパクトなウィジェットのレイアウト原則
- 04ローカル状態と非同期状態の管理
- 05CSPとウィジェットメタデータの確認項目
活用シーン
- 01ウィジェットの実装方法の選択
- 02会話内の単一画面ウィジェットの設計
- 03読み込み中とエラー状態の処理
- 04GPT Appsのウィジェットメタデータの確認
MCP 対応
MCP Appsでは、最小限の設定でReactコンポーネントをext-appsクライアントに表示する方法を説明しています。GPT Appsの設定も扱います。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ xmcp-dev/ skills --skill widget-designインストール後、Claude Code で/widget-designを入力して読み込まれたか確認します。
同梱ファイルreferences/design-principles.md
詳細
ウィジェット設計のベストプラクティス
判断フレームワーク
プラットフォームの選択
対象のクライアントに基づいて選択します。
- GPT Apps:対象はChatGPTです。
_meta.openaiにwidgetAccessible: trueを設定する必要があります。 - MCP Apps:対象は任意のext-appsクライアントです。最小限の設定で、自動的に動作します。
ハンドラーの種類の選択
| シナリオ | ハンドラー | 理由 |
|---|---|---|
| ユーザー操作が必要(ボタン、入力) | React(.tsx) | フックによる状態管理 |
| 外部ウィジェットライブラリの表示 | テンプレートリテラル | スクリプト/スタイルを読み込むだけ |
| ツールのパラメーターからの動的コンテンツ | React | propsを自然に受け渡せる |
| 状態を持たない静的HTML | テンプレートリテラル | よりシンプルで、オーバーヘッドが少ない |
経験則:迷った場合は、Reactから始めましょう。シンプルに始めるより、後から変換するほうが難しいためです。
ウィジェット設計の原則
1. ウィジェットはWebアプリではありません
ウィジェットは会話内にインラインで表示されます。設計上の制約は次のとおりです。
- ナビゲーションなし:1画面で完結する操作のみ
- 幅に制限あり:ほとんどのクライアントで最大約600~700px
- サンドボックス化:外部リソースにはCSP宣言が必要です
- 一時的:再レンダリングされることがあるため、状態の永続性に依存しないでください
2. すぐに価値を提供する
ユーザーの操作を必要とせずに、有用なコンテンツを表示します。
// Bad: Requires click to see anything
export default function Widget() {
const [data, setData] = useState(null);
return <button onClick={fetchData}>Load Data</button>;
}
// Good: Shows data immediately
export default function Widget({ query }) {
const [data, setData] = useState(null);
useEffect(() => { fetchData(query).then(setData); }, [query]);
return data ? <Results data={data} /> : <Loading />;
}
3. 限られたスペースでの視覚的な階層
幅が限られているため、階層構造がより重要になります。
<div className="space-y-4">
{/* Label: small, muted, uppercase */}
<div className="text-sm text-zinc-500 uppercase tracking-wider">Temperature</div>
{/* Value: large, prominent */}
<div className="text-5xl font-light">72°F</div>
{/* Supporting: medium, secondary */}
<div className="text-zinc-400">Feels like 68°F</div>
</div>
4. すべてのインタラクティブ要素にフィードバックを用意する
ユーザーが要素を操作できることを視覚的に確認できるようにします。
// Bad: No visual feedback
<button className="px-4 py-2 bg-white/10">Click</button>
// Good: Hover + transition
<button className="px-4 py-2 bg-white/10 hover:bg-white/20 border border-white/10 hover:border-white/20 transition-all duration-200">
Click
</button>
状態管理のガイドライン
状態をローカルかつシンプルに保つ
ウィジェットは分離されています。Redux、Zustand、外部状態管理は使用しません。
// Good: Local state with hooks
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
3つの状態を常に処理する
すべての非同期処理には3つの状態があります。それらすべてを処理してください。
export default function Widget() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchData()
.then(setData)
.catch(e => setError(e.message))
.finally(() => setLoading(false));
}, []);
if (loading) return <Loading />;
if (error) return <Error message={error} />;
return <Display data={data} />;
}
クリック時ではなくマウント時に取得する
ウィジェットはすぐに価値を提供する必要があります。
// Bad: User must click to see data
<button onClick={() => fetchData()}>Load</button>
// Good: Fetch automatically
useEffect(() => { fetchData(); }, []);
よくある間違い
1. widgetAccessibleの設定漏れ(GPT Apps)
これがないと、ウィジェットは表示されません。
// Broken
_meta: { openai: { toolInvocation: { ... } } }
// Fixed
_meta: { openai: { widgetAccessible: true, toolInvocation: { ... } } }
2. CSPなしで外部データを取得する
リクエストが通知なくブロックされます。
// Broken: fetch fails silently
fetch('{{WEATHER_API_URL}}');
// Fixed: declare in metadata
_meta: {
openai: {
widgetCSP: { connect_domains: ["{{WEATHER_API_BASE_URL}}"] }
}
}
3. 寸法のハードコード
画面サイズが異なると、ウィジェットが正しく表示されません。
// Bad
<div style={{ width: '800px' }}>...</div>
// Good
<div className="w-full max-w-2xl mx-auto">...</div>
4. エラーバウンダリーがない
クラッシュすると、ウィジェットは空白で表示されます。
// Always wrap risky operations
try {
const result = JSON.parse(data);
} catch {
return <Error message="Invalid data format" />;
}
プラットフォーム固有の注意事項
GPT Apps:ウィジェットとの通信には構造化コンテンツを使用する
ツールからウィジェットにデータを渡します。
return {
structuredContent: { game: "doom", url: "..." },
content: [{ type: "text", text: "Launching DOOM..." }],
};
ウィジェットは useToolOutput() フックを使って読み取ります。
MCP Apps:最小限の設定
MCP AppsはReactコンポーネントだけで動作します。
// This is enough for MCP Apps
export const metadata = { name: "widget", description: "..." };
export default function Widget() { return <div>Hello</div>; }
クイックリファレンス
GPT Appsのチェックリスト
- メタデータに
widgetAccessible: trueを設定 toolInvocationメッセージは64文字未満- 外部ドメイン用のCSPを設定
参考資料
references/design-principles.md を参照してください。内容は次のとおりです。
- ウィジェットの完全なサンプル(カウンター、天気、アーケードゲーム)
- コンポーネントのパターン(カード、ボタン、タブ)
- CSS Modulesのサンプル
- GPT Appの申請要件
- プロジェクト構成のテンプレート