Toast
一時的な通知の queue 表示です。fandhe-frontend-headless-ui の toast mod は group(live region)/ root(通知 1 件)/ title / description / action-trigger / close-trigger の 6 anatomy パーツと、複数通知を有界なキューとして管理する状態機械 Toaster を提供します。aria-live は ToastStatus から決定的に導出され(Error のみ assertive、他は polite)、group は role="region" + 必須の aria-label + tabindex="-1" を、root は role="status" + data-state="open"(固定値)+ tabindex="0" を固定付与します。タイマーによる自動 dismiss の実配線は本部品のスコープ外です。
キーボード操作
| キー | 対象 | 効果 |
|---|---|---|
| Tab / Shift+Tab | root → action-trigger → close-trigger | root の tabindex="0" とネイティブ button により、ブラウザ標準でこの順にフォーカス到達します。Radix の F8 / Zag の Alt+T によるリージョンフォーカスは未配線です。 |
| Enter / Space | action-trigger / close-trigger | ネイティブ button の click が発火します。click から "dismiss" dispatch への配線(fandhe-frontend-wasm-full の MAPPING_TABLE)は未実装で、Escape での閉鎖も同様に未配線です。 |
参考サイトとの差分
Zag.js(toast.connect.ts/toast-group.connect.ts、ark-ui の実体)・Radix Primitives Toast・chakra-ui v3 Toast と突合しました(イシュー #1643)。anatomy(6 パート)はいずれの参照軸とも増減なしで一致します。是正した差分は次の 4 点です。
- root に
data-state="open"を追加: Zag・Radix 双方が持ちます。固定値"open"のみを発行し、"closed"(exit 遷移用の中間状態)は発行しません(Toasterに閉鎖・退出中間状態が存在せず、描画された root は構造上つねに open のため)。 - root に
tabindex="0"を追加: Zag・Radix 双方が持ちます。 - group に
tabindex="-1"を追加: Zag group・Radix viewport 双方が持ちます。 - group ラベルの既定値:
Toaster::viewがaria-label=""を出していた不具合をDEFAULT_GROUP_LABEL("Notifications")で是正しました。
action-trigger に data-disabled を発行するかは Themes 側イシュー #1544/#1545 が保留していた判断で、本イシューで発行しないことを確定しました(Zag の getActionTriggerProps は type="button" のみを発行し、ネイティブ disabled 属性を呼び出し側 attrs 経由で渡す運用を維持します)。
意図的に合わせていない点:
- Zag のスタック・遷移・一時停止系
data-*(data-mounted/data-paused/data-first/data-sibling/data-stack/data-overlap)と Radix のスワイプ系(data-swipe/data-swipe-direction)は装飾・アニメーション・ジェスチャ計測の関心であり headless 層へ持ち込みません。 data-placement/data-side/data-alignは group のみに付与し、Zag のように root へは重複付与しません。aria-liveは root に置く方針を維持します(Zag は group に置きますが、本クレートは Radix 準拠の既存方針を維持します)。- root の
aria-labelledby/aria-describedby、title/description の自動idは付与しません(role="status"+aria-atomic="true"で通知全体が読み上げられるため必須ではなく、Radix も付けません)。 - close-trigger の既定
aria-label(Zag の"Dismiss notification")は付与しません(headless-ui のAnatomy::partは呼び出し側 attrs を後置追記するため、固定既定値は利用者指定と二重出力になります。アイコンのみの close-trigger には利用者がattrsでaria-labelを渡してください)。 ToastStatusへのloadingvariant 追加(Zag/chakra)は公開 enum の破壊的変更のため見送りました。- chakra の
Indicator(アイコン)パートは装飾であり headless には置きません(Themes 候補として記録)。 - 要素型は Radix の
ol/liではなく ark-ui と同じdivを維持します。
上記の data-state/tabindex の追加は Themes(fandhe-frontend-pre-styled-ui)側イシュー #1543/#1545 へコメントで共有しました。Themes 側の CSS 変更は不要ですが、exit 遷移(data-state="closed")は fandhe-frontend-wasm-full の dismiss 配線待ちのまま保留です。
スコープ外候補(是正対象ではなく、別途起票を検討する余地がある事項):
fandhe-frontend-wasm-full側の toast 配線(close/action-trigger →"dismiss"dispatch、Escape での閉鎖、ホットキー F8/Alt+T によるリージョンフォーカス、タイマーによる自動 dismiss と hover/focus による一時停止、exit 遷移用data-state="closed"の追加)ToastStatus::Loadingvariant- Themes 側
Indicatorパート
スタイル済みの表示例は Toast を参照してください。
関連 API: fandhe-frontend-headless-ui API
自前 CSS の最小例
[data-scope="toast"][data-part="..."] セレクタでスタイルを当てます。以下は root の枠・影、Error 状態の配色、open 状態のフェードイン、group の固定配置、close-trigger のフォーカスリングの最小例です。
[data-scope="toast"][data-part="root"] {
border: 1px solid #888;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
[data-scope="toast"][data-part="root"][data-type="error"] {
border-color: #dc2626;
background: #fef2f2;
}
[data-scope="toast"][data-part="root"][data-state="open"] {
animation: fd-toast-fade-in 150ms ease-out;
}
@keyframes fd-toast-fade-in {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
[data-scope="toast"][data-part="group"][data-placement="bottom-end"] {
position: fixed;
bottom: 16px;
right: 16px;
}
[data-scope="toast"][data-part="close-trigger"]:focus-visible {
outline: 2px solid #2563eb;
outline-offset: 2px;
}Demo
枠線・余白は docs サイト側のデモ枠であり、fandhe-frontend-headless-ui 自体はスタイルを持ちません。
Features
- group(live region)/ root(通知 1 件)/ title/description/action-trigger/close-trigger の 6 anatomy パーツと、複数通知を有界なキューとして管理する状態機械 Toaster を提供する(dispatch: dismiss/clear)。イシュー #1643 で Zag.js
toast.connect.ts(ark-ui の実体)・Radix Primitives Toast・chakra-ui v3 Toast と突合し、6 パートの増減なしで一致することを確認した。 - aria-live は ToastStatus から決定的に導出する(呼び出し側文字列を直接流し込まない)。Error のみ "assertive"、他は "polite"。aria-atomic="true" を併用し、通知全体を単位として読み上げさせる(Zag は group 側に polite を出すが、本クレートは Radix 準拠で root に置く既存方針を #1643 で維持確定)。
- group は role="region" + aria-label(label は必須引数)+ tabindex="-1" を固定付与する(tabindex は #1643 で追加。Zag/Radix ともプログラム的フォーカス先として持つが、本クレートはホットキー未配線のため Tab 順には入らない)。
- root は role="status" + aria-atomic="true" + aria-live + data-type に加え、data-state="open"(固定値のみ)・tabindex="0" を発行する(#1643 で追加。Zag/Radix ともに持つ属性で、キーボード利用者が Tab で通知本体へ到達できる)。exit 遷移用の data-state="closed" は発行しない。
- action-trigger は data-disabled を発行しない(#1643 で確定。ネイティブ disabled 属性を呼び出し側 attrs 経由で渡す運用)。
- タイマーによる自動 dismiss・Escape での閉鎖・ホットキーによるリージョンフォーカス(Radix F8 / Zag Alt+T)の実配線は fandhe-frontend-wasm-full 側の後続イシューのスコープ。
Anatomy
group
root
title
description
action-trigger
close-triggerAPI Reference
Arguments
| Name | Type | Default | Description |
|---|---|---|---|
group: placement | ToastPlacement | BottomEnd | data-placement へ反映するビューポート角配置(6 語彙)。 |
group: label | &str | — | role="region" とセットの aria-label(呼び出し側が必ず指定する)。 |
root: status | ToastStatus | Info | data-type と aria-live の緊急度(Error のみ assertive、他は polite)を導出する状態。 |
Data Attributes
| Part | Attribute | Observed Values |
|---|---|---|
group | data-placement | bottom-end |
root | data-state | open |
root | data-type | error, success |
Examples
Error status
ToastStatus::Error の Toast は aria-live="assertive" になり、他の状態より即座に割り込んで読み上げられます。
Custom CSS
data-scope / data-part / data-type / data-state / data-placement セレクタで自前 CSS を当てる最小例です。
利用者が data-scope / data-part / data-type / data-state / data-placement セレクタで自前 CSS を当てる最小例です。headless-ui 自体はスタイルを持ちません。
[data-scope="toast"][data-part="root"] {
border: 1px solid #888;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
[data-scope="toast"][data-part="root"][data-type="error"] {
border-color: #dc2626;
background: #fef2f2;
}
[data-scope="toast"][data-part="root"][data-state="open"] {
animation: fd-toast-fade-in 150ms ease-out;
}
@keyframes fd-toast-fade-in {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
[data-scope="toast"][data-part="group"][data-placement="bottom-end"] {
position: fixed;
bottom: 16px;
right: 16px;
}
[data-scope="toast"][data-part="close-trigger"]:focus-visible {
outline: 2px solid #2563eb;
outline-offset: 2px;
}
Accessibility
Keyboard Interactions
| Key | Description |
|---|---|
| Tab / Shift+Tab | root(tabindex="0")→ action-trigger → close-trigger の順にブラウザ標準でフォーカス到達する。Radix の F8 / Zag の Alt+T によるリージョンフォーカスは未配線。 |
| Enter / Space (action-trigger / close-trigger) | ネイティブ button の click が発火する。click から "dismiss" dispatch への配線(fandhe-frontend-wasm-full の MAPPING_TABLE)は未実装で、Escape での閉鎖も同様。 |
WAI-ARIA
| Attribute | Description |
|---|---|
| role="region" / aria-label / tabindex="-1" | group に固定付与(tabindex は #1643 で追加)。 |
| role="status" / tabindex="0" | root に固定付与(tabindex は #1643 で追加)。 |
| aria-atomic="true" | root に固定付与。通知全体を単位として読み上げさせる。 |
| aria-live | root に付与。status から決定的に導出される(Error のみ assertive、他は polite)。Zag は group に置くが本クレートは Radix 準拠で root 側の既存方針を維持する(#1643)。 |
| data-state="open" | root に固定発行(#1643 で追加)。"closed"(exit 遷移用)は発行しない。 |
| close-trigger の aria-label | 既定値は付与しない。アイコンのみの close-trigger には呼び出し側が attrs で aria-label を渡すこと(#1643)。 |