訊息氣泡 (Message Bubble)
- 新提案
- →
- 封閉測試
- →
- 公開測試
- →
- 已穩定
成熟度說明
- 新提案:未完成開發、請勿使用。
- 封閉測試:開發暫時性完成,可使用。可能仍有親和力或其他使用上問題待透過實測發現。
- 公開測試:開發暫時性完成,歡迎使用。具有完整親和力報告。
- 已穩定:開發完成、歡迎使用。應無任何親和力或使用上問題。
基本氣泡
HTML
<!-- 即時宣告元件:全站僅需一個,供訊息氣泡的複製等操作播報使用 -->
<live-announcer></live-announcer>
<message-bubble variant="user">
<article class="message-bubble__inner message-bubble__inner--user" aria-labelledby="mb-basic-user-role">
<p class="message-bubble__role" id="mb-basic-user-role">你</p>
<div class="message-bubble__slot-pre" hidden></div>
<div class="message-bubble__body">請問如何申請自然人憑證?</div>
</article>
</message-bubble>
<message-bubble variant="ai">
<article class="message-bubble__inner message-bubble__inner--ai" aria-labelledby="mb-basic-ai-role">
<p class="message-bubble__role" id="mb-basic-ai-role">客服助理</p>
<div class="message-bubble__slot-pre" hidden></div>
<div class="message-bubble__body">您好!請攜帶國民身分證正本與健保卡,親至任一戶政事務所申請,工本費為新臺幣 250 元。</div>
<p class="message-bubble__error" hidden></p>
<!-- 操作列預設 hidden:JS 啟用後才顯示(無 JS 時讚/倒讚等維持隱藏) -->
<div class="toolbar message-bubble__toolbar" role="toolbar" aria-label="訊息操作" hidden>
<button type="button" class="toolbar__button" data-action="copy" tabindex="0">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
</svg>
<span class="visually-hidden">複製</span>
</button>
<button type="button" class="toolbar__button" data-action="regenerate" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<polyline points="23 4 23 10 17 10"/>
<polyline points="1 20 1 14 7 14"/>
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
</svg>
<span class="visually-hidden">重新生成</span>
</button>
<button type="button" class="toolbar__button toolbar__button--toggle" data-action="like" aria-pressed="false" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"/>
</svg>
<span class="visually-hidden">讚</span>
</button>
<button type="button" class="toolbar__button toolbar__button--toggle" data-action="dislike" aria-pressed="false" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"/>
</svg>
<span class="visually-hidden">倒讚</span>
</button>
</div>
</article>
</message-bubble>- 角色標示為可見文字(「你」「客服助理」),
<article>以aria-labelledby指向它。 - AI 氣泡含操作列:複製、重新生成、讚、倒讚。
模擬串流回覆
HTML
<!-- 即時宣告元件:全站僅需一個 -->
<live-announcer></live-announcer>
<!-- 等待回覆時顯示的輸入中提示,初始 hidden -->
<div class="typing-indicator" data-typing-indicator aria-hidden="true" hidden>
<span class="typing-indicator__dot"></span>
<span class="typing-indicator__dot"></span>
<span class="typing-indicator__dot"></span>
</div>
<!-- AI 回覆的訊息氣泡以 streaming 狀態動態建立、appendToken()/complete() 逐步填入,
見下方「使用方式」;append 到任意容器(如對話容器)皆可 -->- 送出訊息後先由輸入中提示元件顯示等待動畫並播報「AI 正在輸入」;等其
typing-indicator:announced事件確認播報已送達,才開始串流。 - 串流期間正文掛
aria-hidden="true",由本元件依句末標點分句、以至少 1100ms 間隔交由即時宣告代播(節流原因詳見即時宣告頁);完成時播「回覆完成」並顯示操作列。 - 「模擬 AI 回覆」按鈕與「已宣告內容」面板僅供文件示範。
Custom Element
- 使用
<message-bubble>。
使用方式
元件對外只有四個後端無關的方法,任何串流來源都翻譯成這四個呼叫:
const bubble = document.createElement('message-bubble');
bubble.setAttribute('variant', 'ai');
bubble.setAttribute('streaming', '');
container.chatContainer.appendMessage(bubble); // 或直接 append 到任何位置
bubble.appendToken('串流文字片段'); // 追加正文
bubble.complete(); // 回覆完成
bubble.abort(); // 停止生成(保留已生成部分)
bubble.error('連線逾時'); // 錯誤(assertive 播報 + 可見錯誤文字)後端事件對照範例(非規範性,僅為示意):
// Anthropic Messages API(串流)
stream.on('content_block_delta', (event) => bubble.appendToken(event.delta.text));
stream.on('message_stop', () => bubble.complete());// OpenAI Responses API(串流)
for await (const event of stream) {
if (event.type === 'response.output_text.delta') bubble.appendToken(event.delta);
if (event.type === 'response.completed') bubble.complete();
}角色標題與收尾播報文字可用 data-* 屬性覆寫(動態建立與伺服器渲染的靜態氣泡皆支援):
bubble.setAttribute('data-role-label', '王小明'); // 覆寫角色標題,預設「你」/「客服助理」
bubble.setAttribute('data-complete-message', '已回覆完畢'); // 覆寫 complete() 結語,預設「回覆完成」
bubble.setAttribute('data-abort-message', '已取消生成'); // 覆寫 abort() 結語,預設「已停止產生回覆」CSS
.message-bubble__inner/__inner--user/__inner--ai:氣泡本體與角色 modifier。.message-bubble__role:可見角色標題。.message-bubble__toolbar:操作列,樣式沿用工具列元件。
親和力
- 角色標示為可見文字,不靠顏色或對齊區分。
- 串流正文的播報由「即時宣告」元件分句代播,正文本身不掛任何
aria-live。 - 操作列沿用工具列模式:單一 Tab 停駐點、方向鍵移動。
- 讚/倒讚為互斥 toggle(
aria-pressed),再按一次可取消。 - 操作列按鈕標籤(複製/重新生成/讚/倒讚)、工具列的
aria-label="訊息操作",以及複製失敗時的提示文字,為刻意固定的無障礙輔助文字,目前不提供覆寫。
JavaScript
元件以 ES module 撰寫,載入必須加 type="module"。(若因架構需要跨網域載入,伺服器須提供 CORS 標頭,否則會靜默失敗。)路徑請依實際部署位置調整。
<script type="module" src="js/components/message-bubble.js"></script>- 本元件會自動載入同目錄的
toolbar.js,部署時需一併放置;頁面需掛載即時宣告元件。 - 複製成功後有 polite 播報;重新生成與讚/倒讚以
message-bubble:regenerate/message-bubble:feedback事件通知外部,元件本體不重送請求。 - 無 JS 時:靜態氣泡(角色標題、正文)可讀;操作列(含讚/倒讚)維持隱藏。本元件未內建表單模式;若伺服器端支援表單提交,可自行將讚/倒讚改造為表單版本。
多格式文字段落
- 氣泡正文可放入語意化 HTML(如表格、清單):由伺服器或產品層渲染完成後置入,螢幕閱讀器以原生語意閱讀。
appendToken()僅支援純文字;含結構化內容的回覆,建議串流階段以純文字呈現,complete()後由產品層替換正文內容。- 即時宣告僅播純文字,表格與清單內容不會透過 live region 朗讀;建議完成時以文字摘要宣告(如「回覆包含表格」),內容由使用者於對話紀錄中瀏覽。
- 元件不會解析 Markdown;若由呼叫端自行轉換為 HTML 後傳入,該 HTML 須先經過淨化(sanitize)以避免 XSS 風險。
參考
- Defining ‘Toast’ Messages - Adrian Roselli
- Accessible notifications with ARIA Live Regions (Part 2) - Sara Soueidan
- Toolbar Pattern - ARIA Authoring Practices Guide