メインコンテンツへスキップ
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
はじめにWebReactSwiftUIJetpack ComposeFlutterReact Nativeプリセット自分のデザインシステムを作るAI エージェントと Tanrei を使うブランドデザイントークンデザインシステムとカスタマイズTheme Studioカラータイポグラフィアイコン形状エレベーションモーションインタラクション状態振る舞いレイアウトアクセシビリティ言葉カラートークンタイプスケールトークン形状トークンエレベーショントークンモーショントークン状態トークンスペースと寸法のトークンコンポーネントボタンメインアクションボタンセグメントボタンボタングループメニュー付きボタンツールバーコピーボタンチェックボックスラジオボタンスイッチチップメニュースライダースライダーフィールドカレンダーDateTimePickerフィールドグループラジオグループチェックボックスグループセレクトセレクトフィールドメニューバーファイルアップロードファイルピッカー 上級者向けドロップゾーンテキスト入力欄 上級者向けテキストフィールドチップフィールドフィールドコンボボックス 上級者向けコンボボックスフィールド数値入力欄 上級者向け数値フィールドカラー入力欄 上級者向けカラーフィールドワンタイムコード入力欄 上級者向けワンタイムコードフィールド埋め込みテキスト入力欄 上級者向けチャットコンポーザーカード区切り線モーダルレイヤー 上級者向けダイアログリストリスト行ボトムシートサイドシートカルーセルコードブロックテーブルポップオーバーステップアコーディオン説明リスト図アプリシェルページヘッダーツリービュータイムライン画像プレビューハンドルサイズ変更できる枠スクロールエリアバッジプログレスインジケーターメーターナンバーティッカーローディングインジケータースナックバーツールチップキーボードショートカットタグインラインコードコールアウトアバター空の状態タググループスケルトンファイル項目色見本マーキーテキストタブタブビューアプリバーナビゲーションバーナビゲーションドロワー検索リンクページネーション前後のリンクステッパーパンくずリスト目次リンクカードFAB メニューステータスTanrei の作り方コントリビューションライセンスと帰属
ホームコンポーネントスナックバー

スナックバー

アプリがいま何をしたかを、画面の下端、または上端の終わり側で短く伝えます。アクションは多くても 1 つで、フォーカスを奪わず、作業を中断させません。

content_copyMarkdown で表示
カテゴリ
コミュニケーション
トークン
comp.snackbar
レベル
基礎

概要

Web
tanrei-snackbar
schedule
SwiftUI
TanreiSnackbar
schedule
Jetpack Compose
TanreiSnackbar
schedule
Flutter
TanreiSnackbar
schedule
React Native
Snackbar
schedule

生成元 docs/status/status.json, specs/snackbar.spec.json

仕様は改訂済み、各プラットフォームは追従待ち

このページは、仕様の現在の形のスナックバーを説明しています。バーをスキームのサーフェスの上に、または反転(inverse)して描き、アクションは控えめな tonal になり、状態の種類は淡い色合いで描き、タイトルと長い文章の展開が加わりました。Web はこのすべてを描きます。上の表で Web が「追従待ち」のままなのは、反転した状態の種類の確認と、そのベースラインの記録が済んでいないためです。SwiftUI、Compose、Flutter、React Native は、それより前の版(テキストのアクション、状態の種類・タイトル・展開・inverse なし)を提供しており、追従するまで、それぞれのサンプルもその版を示します。デモは、現在提供している Web コンポーネントです。

その場に描いたもの:アクション付きのメッセージと、閉じるボタン付きのメッセージ

使い方

スナックバーは利用者側で配置するものではありません。ホストに表示を頼むと、ホストがキューに入れ、ホストの領域の下端(または上端の終わり側)に置き、時間切れを管理し、読み上げます。show は、そのスナックバーが閉じたときに結果を返します。アクションが実行されたなら action、そうでなければ dismiss と理由です。そのため「元に戻す」の処理を上から下へ順に書けます。

プラットフォーム ホスト 表示のしかた
Web <tanrei-snackbar-host>。ページの最後の要素として置きます host.show(options)。Promise を返します
SwiftUI TanreiSnackbarHost。.tanreiSnackbarHost(_:) で付けます await host.show(message:…)
Compose TanreiSnackbarHost(hostState)。コンテンツと同じ Box の最後に置きます hostState.showSnackbar(message, …)。suspend 関数です
Flutter TanreiSnackbarMessenger。対象のページを包みます TanreiSnackbarMessenger.of(context).show(TanreiSnackbar(…))。closed が Future のコントローラーを返します
React Native TanreiProvider がオーバーレイ層に描きます useSnackbar().show(options)。Promise を返します

どのホストにも、表示中のスナックバーを閉じる dismissCurrent と、待っているものも破棄する clear があり、どちらも理由は host です。ホストの外で描いたスナックバーの要素は、その場に描かれ、ホストの中と同じく与えられた幅の中央に置かれます(placement が top-end なら、その幅の終わり側に置かれます)。時間切れ、キュー、登場の動き、読み上げのいずれもありません。上のデモのように、ドキュメントのページやテストのための形です。

<!-- ページの最後に置きます。フォーカス順でのホストの位置に、スナックバーのコントロールが入ります。 -->
<tanrei-snackbar-host></tanrei-snackbar-host>

<script type="module">
  import '@tanrei/web/components/snackbar';
  const snackbars = document.querySelector('tanrei-snackbar-host');

  async function archive() {
    await archiveConversation();
    // 閉じたときに { result: 'action' } か { result: 'dismiss', reason } で解決します。
    const { result } = await snackbars.show({ message: '会話をアーカイブしました', action: '元に戻す' });
    if (result === 'action') await restoreConversation();
  }

  // 状態の種類:グリフと、支援技術向けに言葉で表した状態。
  snackbars.show({
    status: 'error',
    statusLabel: 'エラー',
    title: 'メッセージを送信できませんでした',
    message: 'オフラインです。メッセージは送信トレイに残ります。',
    action: '再試行',
    expandLabel: 'メッセージ全体を表示',
  });

  // デザインシステムの選択にかかわらず、反転して描きます。`inverse` を省くと、システムの選択に従います。
  snackbars.show({ message: 'リンクをコピーしました', inverse: true });
</script>
import { useRef } from 'react';
import { SnackbarHost } from '@tanrei/react';
import type { TanreiSnackbarHost } from '@tanrei/web';

// ref は要素そのものです。show() はそのメソッドです。
const snackbars = useRef<TanreiSnackbarHost>(null);

async function archive() {
  await archiveConversation();
  const { result } = await snackbars.current!.show({ message: '会話をアーカイブしました', action: '元に戻す' });
  if (result === 'action') await restoreConversation();
}

// 状態の種類と、メッセージの上のタイトル。
snackbars.current?.show({ status: 'error', statusLabel: 'エラー', title: 'メッセージを送信できませんでした', message: 'オフラインです。', action: '再試行' });

// デザインシステムの選択にかかわらず、反転して描きます。
snackbars.current?.show({ message: 'リンクをコピーしました', inverse: true });

// コンテンツの後ろ、ページの最後に置きます。
<SnackbarHost ref={snackbars} />
import Tanrei

@State private var snackbars = TanreiSnackbarHost()

// 下端を使うビューにホストを付けます。シートではなくオーバーレイに描きます。
ContentView()
    .tanreiSnackbarHost(snackbars)

// スナックバーが閉じたときに .action か .dismissed(reason) を返します。
// ホストを付けたビューが画面にない間は、表示の依頼はそのまま待ちます。
let result = await snackbars.show(message: "会話をアーカイブしました", actionLabel: "元に戻す")
if case .action = result { restoreConversation() }
import io.github.chnotchy.tanrei.components.TanreiSnackbarHost
import io.github.chnotchy.tanrei.components.TanreiSnackbarHostState
import io.github.chnotchy.tanrei.components.TanreiSnackbarResult

// Material の SnackbarHostState と同じく、showSnackbar はスナックバーが閉じるまで中断します。
val snackbars = remember { TanreiSnackbarHostState() }
val scope = rememberCoroutineScope()
Box(Modifier.fillMaxSize()) {
    Content(onArchive = {
        scope.launch {
            when (snackbars.showSnackbar("会話をアーカイブしました", actionLabel = "元に戻す")) {
                TanreiSnackbarResult.ActionPerformed -> unarchive()
                is TanreiSnackbarResult.Dismissed -> Unit
            }
        }
    })
    // Box の最後に置くと、コンテンツより上に描かれ、フォーカス順でも最後になります。
    TanreiSnackbarHost(snackbars, Modifier.fillMaxSize())
}
import 'package:tanrei/tanrei.dart';

// ScaffoldMessenger と同じ形です。ルートの中でページを包むと、上に開いたダイアログがスナックバーを覆います。
TanreiSnackbarMessenger(child: page);

Future<void> archive(BuildContext context) async {
  await archiveConversation();
  final TanreiSnackbarController snackbar = TanreiSnackbarMessenger.of(context).show(
    const TanreiSnackbar(message: '会話をアーカイブしました', actionLabel: '元に戻す'),
  );
  // action、close、escape、timeout、host のいずれかで完了します。
  if (await snackbar.closed == TanreiSnackbarClosedReason.action) {
    await restoreConversation();
  }
}
import { useSnackbar } from '@tanrei/react-native';

// ホストは TanreiProvider のオーバーレイ層にあります。プロバイダーはアプリのルートに置きます。
const snackbar = useSnackbar();

async function archive() {
  await archiveConversation();
  const { result } = await snackbar.show({ message: '会話をアーカイブしました', actionLabel: '元に戻す' });
  if (result === 'action') await restoreConversation();
}

仕様

折り返すメッセージと、次の行に回ったアクション

構成要素

構成要素

Web パッケージが描くフィクスチャ message の上に描いています。
パーツ 説明
1 container The bar that holds the text and the controls, raised above the content of its host. Its color is its form's: a surface of the scheme (the default form, or a faint tint of a status) or, drawn inverse, the opposite scheme's surface or, in a status form, the bar the other theme draws for that status: deep in a light scheme, pale in a dark one.
2 outline · 任意 The edge of a status form: a hairline in the status's color, at the edge's opacity, around the bar where the ring of its elevation is. The default form and the inverse form draw none and keep that ring alone.
3 icon · 任意 The status glyph from the system's icon set, before the text: info, warning, error or check_circle. Drawn only in a status form, a little softer than the text (in the status's own color on a surface and, on an inverse bar, in the status color of the other theme, at the glyph's opacity), and hidden from assistive technology, which hears statusLabel instead.
4 title · 任意 A few words that name what happened, above the message. Up to three lines while collapsed.
5 supporting-text The message: what happened, or with a title the description, which is then drawn a little quieter than the title. Up to three lines while collapsed.
6 action · 任意 One control that undoes or follows up on what happened, drawn as a tonal button with the snackbar's own tokens, as tall as the small Buttons beside it: the color of its own bar with a tonal layer of its label's color over it, so that it stands one quiet step off the bar, the same step in every form.
7 expand · 任意 An icon-only Button of the small size that shows the whole title and message, and hides them again: the glyph expand_more while collapsed and expand_less while expanded. Drawn only while the text is longer than its collapsed limit, and drawn as the close button is (SNK-34).
8 close · 任意 An icon-only Button of the small size showing the icon set's close glyph, which closes the snackbar. The snackbar places it, chooses its variant (comp.snackbar.close-button.variant: tonal in the default system) and gives it its form's tone (BTN-45): the glyph in the bar's message color and, where the variant draws a container, a circle of that color made translucent. Everything else it reads from Button's own tokens.
9 focus-ring · 任意 Gap-then-ring indicator shown for keyboard focus of the action, or of the text while it scrolls, in the color its form gives it so that it shows on that form's bar. The expand and close buttons draw their own, in the same color (SNK-34).

寸法

寸法

長さ トークン 値(px)
container padding-inline-start tanrei.comp.snackbar.container.padding-inline-start 16
container padding-inline-end tanrei.comp.snackbar.container.padding-inline-end 16
Web パッケージが描くフィクスチャ message の上に描いています。

プロパティ

プロパティ 型 既定値 説明
message string "" What happened; with a title, the description. Required. Plain text: the whole message is announced, even when a collapsed snackbar cuts it.
title string "" A few words above the message that name what happened ("Message not sent"). Empty draws the message alone.
action string "" The label of the one action ("Undo", "View"), drawn as a tonal button that sits one quiet step off the bar, the same step in every form. Empty draws no action.
status default | info | warning | error | success "default" The form of the snackbar. Default is the neutral bar with no glyph; info, warning, error and success draw the bar as a quiet tint of that status (pale on the scheme's surface or, drawn inverse, the bar the other theme draws for that status, with that theme's text, glyph and edge colors), with an edge in the status's color and the status's glyph before the text, and warning and error are announced assertively.
statusLabel string "" The status in words, in the product's language ("Error"), announced before the title and the message so that the status never rests on color and glyph alone. Required while status is not default.
inverse boolean null Whether this snackbar is drawn inverse: in the opposite scheme's colors (a dark bar in a light scheme), so that it stands apart from whatever it lands on. False draws it on a surface of the scheme. Null, the default, takes comp.snackbar.container.inverse, the system's choice, which is off in the default system. It changes colors only (SNK-30).
closable boolean false Draws a close button, for a message that stays until the person closes it or for one they may want gone sooner.
closeLabel string "" The close button's accessible name, in the product's language ("Close"). Required while closable is true.
expanded boolean false Whether the whole title and message show. The person toggles it with the expand button (SNK-24); it has no effect while the text fits within its collapsed limits.
expandLabel string "" The expand button's accessible name, in the product's language ("Show full message"). It stays the same while expanded; the button's expanded state says which way it acts. Required whenever the text can be longer than its collapsed limit.
duration auto | short | long | indefinite "auto" How long the snackbar stays by itself. Auto is short without an action and indefinite with one (WCAG 2.2.1: a person must not be timed out of an action). Short and long close after the snackbar's timeout tokens; indefinite stays until it is closed.
placement bottom | top-end "bottom" Where the host places the snackbar in its area. Bottom, the default, is the bottom edge, centred (SNK-13). Top-end is the top edge at the end side: the top right in a left-to-right layout and the top left in a right-to-left one, and across the top of a compact window (SNK-35). A host may be given one placement for all the snackbars it shows; a snackbar that names its own keeps it. It changes where the snackbar stands and the side it enters from (SNK-36, SNK-37), and nothing else: not its size, its form, its order in the queue, its place in the focus order or its announcement.

イベント

イベント 説明
action The action was activated. The snackbar closes.
dismiss The snackbar closed without its action; carries the reason: close (the close button), escape, timeout, or host (the product closed it or cleared the queue).
toggle The person expanded or collapsed the text with the expand button; carries the new expanded value. Not emitted when the product sets expanded.

状態

振る舞い

EARS 記法による要件です。各プラットフォームの実装は、この ID に対応するテストで検証されます。要件の原文は仕様ファイルのものをそのまま掲載しています。

ID 要件
SNK-1 WHEN the user activates the action by pointer, touch, Enter or Space THE snackbar SHALL emit action exactly once and close; WHEN the user activates the close button the same way THE snackbar SHALL emit dismiss with the reason close exactly once and close.
SNK-2 WHILE the action is hovered, keyboard-focused or pressed THE snackbar SHALL render the action's container, content color and opacity from the component tokens of the highest-precedence active state of the action, in the snackbar's form (SNK-21, SNK-30), with precedence pressed over focused over hovered; focus gained by pointer or touch SHALL NOT select the focused state. The expand and close buttons answer their states as any Button does (SNK-34).
SNK-3 WHEN the action receives keyboard focus THE snackbar SHALL show the focus ring around it without animation; focus gained by pointer or touch SHALL NOT show it. THE snackbar SHALL draw the outer stroke of that indicator, of the indicator inside the text while it scrolls and has keyboard focus (SNK-25) and, through the tone it gives them (SNK-34), of the indicators of the expand and close buttons, in its form's focus-ring color: comp.snackbar.focus-ring.color in the default form, and the token of that name under the form's prefix in every other (comp.snackbar.info.focus-ring.color, comp.snackbar.inverse.focus-ring.color, comp.snackbar.inverse-info.focus-ring.color, and warning's, error's and success's of the same names), never the system's focus-ring color role read directly. The widths and the offset SHALL stay the system's (sys.focus.outer-width, sys.focus.inner-width, sys.focus.offset), as SHALL the color of an inner stroke where the system draws one; under the glow focus style (SNK-7) the form's color SHALL be the halo's outer color.
SNK-4 WHERE the visual size of the action is below the minimum target THE snackbar SHALL extend the action's hit area on both axes to sys.target.touch for touch input and sys.target.pointer for fine pointers, without changing its visual size; a platform that cannot tell the input type SHALL use sys.target.touch.
SNK-5 WHEN the action is pressed by pointer, touch, Enter or Space THE snackbar SHALL show the pressed state without delay and SHALL keep it visible for at least sys.motion.duration.press-hold, even when the press ends sooner.
SNK-6 WHILE the action is pressed THE snackbar SHALL answer as sys.behavior.pressed-feedback names: overlay applies the pressed tokens to the whole container of the action at once; ripple grows the pressed state layer from the point of contact (from the center for keyboard activation); dim applies the pressed opacity to the whole action and draws no state layer; none draws no answer to the press. Under ripple and dim only the container color stays with the state under the press; content color, opacity and shape still come from the pressed tokens. Under none every part stays with the state under the press. The ripple grows over comp.snackbar.motion.feedback-duration with comp.snackbar.motion.state-easing, fades over comp.snackbar.motion.state-duration with comp.snackbar.motion.state-easing once the press has ended and press-hold has passed, and appears whole where motion is reduced.
SNK-7 THE snackbar SHALL ease its container corners and the action's corners by sys.shape.corner-smoothing (0 is a quarter circle, 0.6 the iOS continuous corner; a platform with only the OS curve draws it for any amount above 0) and show its keyboard focus as sys.behavior.focus-style names (ring or glow); a platform that cannot draw a choice SHALL fall back to the first value and state that in its README.
SNK-8 WHILE duration is auto THE snackbar SHALL behave as short when action is empty and as indefinite when action is set. WHILE duration is short or long THE snackbar SHALL close by itself once it has been shown for comp.snackbar.timeout.short or comp.snackbar.timeout.long, and emit dismiss with the reason timeout; WHERE the platform recommends a longer time for the person's accessibility settings (Android's accessibility timeout) it SHALL use that time, and WHERE the platform reports that a screen reader is running a snackbar with an action SHALL NOT close by itself. WHILE duration is indefinite THE snackbar SHALL NOT close by itself.
SNK-9 WHILE the pointer is over the snackbar, focus is inside it, it is expanded, the app's window is hidden or in the background, or a Tanrei modal dialog covers its host THE snackbar SHALL pause its timeout, and WHEN none of these holds any longer SHALL start the timeout again from its whole length.
SNK-10 WHEN a snackbar is shown THE snackbar SHALL have assistive technology announce it once: statusLabel first when status is not default, then the title when it has one, then the whole message (never only the part a collapsed snackbar shows), then the action's label when it has one. It SHALL announce politely (after what is being read) when status is default, info or success, and assertively (interrupting what is being read) when status is warning or error, and SHALL NOT move keyboard or screen-reader focus.
SNK-11 THE snackbar's controls SHALL be reachable in the focus order at the place its host has in that order, in this order whatever row each is drawn on: the text while it scrolls (SNK-25), the action, the expand button, the close button; WHEN the user presses Escape while focus is inside the snackbar THE snackbar SHALL emit dismiss with the reason escape and close; WHEN a snackbar closes while focus is inside it, focus SHALL return to the element that had it before focus entered the snackbar or, where that element no longer exists, to the platform's default.
SNK-12 THE host SHALL show one snackbar at a time, in the order they were requested: WHEN a snackbar is requested while another is showing or closing it SHALL wait until the other has closed, and WHILE a Tanrei modal dialog covers the host a snackbar not yet shown SHALL wait until the dialog closes. Each request SHALL resolve once its snackbar has closed, with action or with dismiss and its reason. WHEN the product closes the showing snackbar THE host SHALL close it and resolve it with dismiss and the reason host, and the next waiting one SHALL then show; WHEN the product clears the queue THE host SHALL also drop every waiting one and resolve each with dismiss and the reason host.
SNK-13 WHILE placement is bottom THE host SHALL place the snackbar at the bottom of its area, centred, comp.snackbar.container.margin above the bottom edge and at least comp.snackbar.container.margin from the side edges, inside the safe area and above the on-screen keyboard; at either placement it SHALL draw the snackbar above its area's content and below a Tanrei modal dialog and its scrim.
SNK-14 THE snackbar SHALL be at least comp.snackbar.container.min-height tall and as wide as its content between comp.snackbar.container.min-width and comp.snackbar.container.max-width, but never wider than its host less comp.snackbar.container.margin on each side. It SHALL draw comp.snackbar.container.color, or its form's container color (SNK-21, SNK-30), with comp.snackbar.container.elevation and comp.snackbar.container.shape corners, and pad its content by comp.snackbar.container.padding-inline-start before the status glyph or, without one, before the text, by comp.snackbar.container.padding-block below the text and by at least as much above it, where SNK-29 places the first line, and after the text by comp.snackbar.container.padding-inline-end when there is no control or by comp.snackbar.container.padding-inline-end-with-controls after the last control. WHILE the action stands on its own row (SNK-16) and no control follows the text on its row, the text SHALL end comp.snackbar.container.padding-inline-end from the end edge, as in a snackbar without controls, and padding-inline-end-with-controls SHALL be the action row's alone.
SNK-15 THE snackbar SHALL draw the message in comp.snackbar.supporting-text.typescale and comp.snackbar.supporting-text.color, or its form's message color (SNK-21, SNK-30), at the opacity SNK-32 gives a message under a title, followed comp.snackbar.container.gap after the text (the title and the message) by the action, the expand button and the close button, in that order, comp.snackbar.controls.gap apart and centred on the text's first line (SNK-29). The action SHALL be drawn as a tonal button, in its form's action colors: a container of comp.snackbar.action.container.color at least comp.snackbar.controls.height tall (the height of a small Button, so that the action stands as tall as the expand and close buttons in its row), the tonal layer over it (SNK-33), and comp.snackbar.controls.padding-inline around its label in comp.snackbar.controls.label.typescale and comp.snackbar.action.content.color, on comp.snackbar.controls.shape corners. The expand and close buttons SHALL be drawn as SNK-34 says.
SNK-16 WHEN the text (the title and the message) would need more than two lines beside the controls, or the action's label would itself wrap, THE snackbar SHALL move the action onto its own row below the text, comp.snackbar.container.gap below it and aligned to the end, with comp.snackbar.container.padding-inline-end-with-controls between that row and the bar's bottom edge, while the expand and close buttons stay at the end of the first row; the text SHALL wrap, and is cut only as SNK-23 says, and the action's label SHALL never wrap or be truncated.
SNK-17 WHEN a snackbar is shown THE snackbar SHALL rise into place from below the bottom edge of its host by its own height plus its margin while fading in (WHILE placement is bottom; at top-end it descends instead, SNK-36), over comp.snackbar.motion.enter-duration with comp.snackbar.motion.enter-easing; WHEN it closes it SHALL fade out in place over comp.snackbar.motion.exit-duration with comp.snackbar.motion.exit-easing, and the host SHALL show the next one only after that. WHERE motion is reduced it SHALL fade in and out without moving, or appear and disappear at once on a platform whose reduced-motion setting removes every animation (Android's).
SNK-18 WHEN the layout direction is right-to-left THE snackbar SHALL mirror its layout (the status glyph and the text at the right, the controls at the left) without mirroring any glyph, and SHALL grow a ripple from the point of contact as in left-to-right.
SNK-19 WHILE a snackbar is drawn outside a host (a snapshot harness, a documentation page) THE snackbar SHALL draw in place at its resting position, centred across the width it is given as SNK-13 centres it in a host (or, WHILE placement is top-end, at the end of that width as SNK-35 places it, and across the whole of it where that width is narrower than comp.snackbar.breakpoint.medium) and at most as tall as the height it is given, the width and the height it is given standing for the host's area in SNK-25 and SNK-35; it SHALL NOT time out, queue, animate its entrance or announce itself, and SHALL still emit action, dismiss and toggle.
SNK-20 WHILE message is empty THE snackbar SHALL still render and in debug builds report the empty message; WHILE closable is true and closeLabel is empty it SHALL still render the close button and in debug builds report that the button has no accessible name; WHILE status is not default and statusLabel is empty it SHALL still render the status form and in debug builds report that the status is not given in words; WHILE the expand button is drawn and expandLabel is empty it SHALL still render it and in debug builds report that it has no accessible name; WHEN duration is short or long and action is set it SHALL, in debug builds, remind the product that a timed action must also be offered elsewhere.
SNK-21 WHILE status is info, warning, error or success THE snackbar SHALL draw that status form: the bar, the title, the message and the status glyph in the status's container, title, supporting-text and icon colors, and every state of the action from the parts prefixed with the status, and give the expand and close buttons the status's tone (SNK-34). For info, while the snackbar is not drawn inverse (SNK-30), these are comp.snackbar.info.container.color, comp.snackbar.info.title.color, comp.snackbar.info.supporting-text.color, comp.snackbar.info.icon.color, comp.snackbar.info.outline.color for its edge (SNK-31), comp.snackbar.action.info.container.color and comp.snackbar.action.info.content.color; warning, error and success read their own tokens of the same names, and a status form drawn inverse reads the tokens SNK-30 names. Before the text it SHALL draw the status's glyph from the system's icon set (info for info, warning for warning, error for error, check_circle for success) at comp.snackbar.icon.size, in its form's icon color drawn at comp.snackbar.icon.opacity (an opacity of the glyph alone, which fades nothing else), comp.snackbar.icon.gap before the text and centred on the text's first line (SNK-29), with the family and fill of an unselected icon, sys.icon.weight and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max, never mirrored. The glyph so drawn SHALL keep 3:1 against its bar in every scheme of the default system. The glyph SHALL be hidden from assistive technology, and statusLabel SHALL be exposed as text before the title and the message. WHILE status is default THE snackbar SHALL draw no glyph and read the unprefixed tokens, or drawn inverse the tokens prefixed with inverse (SNK-30).
SNK-22 WHILE title is not empty THE snackbar SHALL draw it above the message in comp.snackbar.title.typescale and comp.snackbar.title.color, or its form's title color (SNK-21, SNK-30), with comp.snackbar.title.gap between the title and the message; WHILE title is empty it SHALL draw the message alone. THE snackbar SHALL set the title's letter-spacing by the script of its text, as every title is set (PRS-20): no negative letter-spacing WHERE its language is Chinese, Japanese or Korean, and none WHERE its script joins its letters or stacks marks on them.
SNK-23 WHILE expanded is false THE snackbar SHALL show at most comp.snackbar.title.max-lines lines of the title and at most comp.snackbar.supporting-text.max-lines lines of the message, each ending with an ellipsis where it is cut, at every text size, and the whole of both SHALL still reach assistive technology. WHILE the title or the message is longer than its collapsed limit THE snackbar SHALL draw the expand button, holding expand_more while collapsed and expand_less while expanded; WHILE both fit within their limits it SHALL NOT draw the expand button, and expanded SHALL have no effect. WHILE expanded is true THE snackbar SHALL show the whole title and message.
SNK-24 WHEN the user activates the expand button by pointer, touch, Enter or Space THE snackbar SHALL toggle expanded, emit toggle with the new value exactly once, keep focus on the expand button and stay open; the bar SHALL grow to its expanded height or shrink back over comp.snackbar.motion.expand-duration with comp.snackbar.motion.expand-easing, keeping its bottom edge in place WHILE placement is bottom and its top edge at top-end (SNK-37), and change at once where motion is reduced. The expand button SHALL expose to assistive technology whether the text is expanded and which text it shows and hides, and SHALL keep expandLabel as its name in both states. WHEN the product sets expanded THE snackbar SHALL change the same way without emitting toggle.
SNK-25 WHILE the snackbar is expanded and its whole content would make it taller than its host's area allows (the area's height less comp.snackbar.container.margin above and below, inside the safe area and above the on-screen keyboard) THE snackbar SHALL take that height and scroll its text inside the bar, keeping the status glyph, the controls and an action on its own row in place; WHILE the text scrolls it SHALL be reachable by keyboard (a focus stop, scrolled by the arrow keys, Page Up and Page Down), named by the title when there is one, and scrolled by wheel, trackpad and touch.
SNK-26 WHILE the system's icon set cannot yet draw a glyph of the snackbar (its font is still loading) — the status glyph, or the glyph it gives the expand or the close button — THE snackbar SHALL draw nothing in that glyph's place, never the glyph's name as text, SHALL keep the glyph's box, and SHALL draw the glyph once the set can; statusLabel, expandLabel and closeLabel SHALL still reach assistive technology.
SNK-27 THE snackbar SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own; WHEN that direction changes while the snackbar is shown THE snackbar SHALL draw itself for the new direction at once, without a motion of its own, keeping its open state, the time left before it closes and its focus.
SNK-28 THE snackbar SHALL lay out its title, its message and its action's label as text of its own direction, isolated from the text around it: each takes the base direction of its first strong character, or the layout direction where it has none, so that a left-to-right word, a number with its sign or unit, a telephone number and an address keep their own order in a right-to-left layout and reorder nothing beside them; THE snackbar SHALL still place each and align its lines by the layout direction, not by the text's own, and SHALL cut a title or a message cut by a line limit that does not fit (SNK-23) at the end of its own direction, so that a left-to-right label in a right-to-left layout loses its right end and never its beginning.
SNK-29 THE snackbar SHALL align its content to the top of the bar. It SHALL centre the first line of the text (the title's, or the message's without a title) half of comp.snackbar.container.min-height below the bar's top edge, or lower where comp.snackbar.container.padding-block plus half that line's height is more (enlarged text), and SHALL centre the status glyph and every control of the first row (the action while it is beside the text, the expand button and the close button) on that line; every further line of the text follows below it, an action on its own row (SNK-16) follows the text, and any height the bar has beyond its content SHALL lie below the content, never above it. WHEN the bar expands or collapses (SNK-24), and WHEN its text is cut or shown whole (SNK-23), the status glyph, the first line of the title and of the message, and every control of the first row SHALL keep their distance from the bar's top edge, during the motion and after it; an action on its own row SHALL keep its distance from the bar's bottom edge.
SNK-30 THE snackbar SHALL be drawn inverse WHILE inverse is true, or WHILE inverse is null and comp.snackbar.container.inverse is on; otherwise (inverse false, or null where that choice is off) it SHALL be drawn on a surface of the scheme, from the unprefixed tokens or its status form's (SNK-21). WHILE it is drawn inverse and status is default THE snackbar SHALL draw the bar, the title and the message in comp.snackbar.inverse.container.color, comp.snackbar.inverse.title.color and comp.snackbar.inverse.supporting-text.color, and every state of the action from the parts prefixed with inverse (comp.snackbar.action.inverse.container.color and comp.snackbar.action.inverse.content.color), and give the expand and close buttons that form's tone (SNK-34). WHILE it is drawn inverse and status is info, warning, error or success it SHALL draw that status's inverse form the same way, from the parts prefixed with inverse- and the status: for info comp.snackbar.inverse-info.container.color, comp.snackbar.inverse-info.title.color, comp.snackbar.inverse-info.supporting-text.color, comp.snackbar.inverse-info.icon.color, comp.snackbar.inverse-info.outline.color for its edge (SNK-31), comp.snackbar.action.inverse-info.container.color and comp.snackbar.action.inverse-info.content.color, and for warning, error and success their own tokens of the same names. Under the default system each color of a status's inverse form SHALL be the color the same part of that status's form not drawn inverse has in the scheme of the other theme at the same contrast level (light for dark, light-medium for dark-medium, light-high for dark-high, and each the other way): the bar, the title, the message, the glyph, the edge, the focus indicator, every state of the action and the tone of the expand and close buttons, so that the inverse form of a status in one theme and its form on a surface in the other differ only in what lies around the bar. Being drawn inverse SHALL change colors only: the bar's elevation, corners, sizes and layout, the glyphs, the width and the opacity of a status form's edge, the opacities of the glyph, of a message under a title and of the action's tonal layer, the motion, the timing, the announcement and the keyboard behaviour SHALL be those of the same snackbar not drawn inverse. WHEN inverse or the system's choice changes while the snackbar is shown THE snackbar SHALL draw the other form at once, keeping its open state, the time left before it closes and its focus.
SNK-31 WHILE status is info, warning, error or success THE snackbar SHALL draw its form's edge: a line comp.snackbar.outline.width wide around the bar, outside the bar's bounds and following its corners, where the ring of comp.snackbar.container.elevation lies (the layer of that elevation with no offset, no blur and a positive spread), over that ring and above the elevation's shadows, in the form's outline color drawn at comp.snackbar.outline.opacity: comp.snackbar.info.outline.color for info, comp.snackbar.inverse-info.outline.color for info drawn inverse (SNK-30), and warning's, error's and success's tokens of the same names. The edge SHALL NOT change the bar's size or the place of anything inside or around it, and the focus indicator of a control SHALL be drawn above it. WHILE status is default THE snackbar SHALL draw no edge of its own, drawn inverse or not: the ring of its elevation shows alone, as on every raised surface. WHERE comp.snackbar.outline.opacity is 0 a status form SHALL draw no edge either.
SNK-32 WHILE title is not empty THE snackbar SHALL draw the message at comp.snackbar.supporting-text.under-title.opacity: an opacity of the message's text alone, applied to its form's message color (SNK-15), in every form, collapsed and expanded. WHILE title is empty it SHALL draw the message whole, and it SHALL draw the title whole in every form. The message so drawn SHALL keep 4.5:1 against its bar in every scheme of the default system, and 7:1 in its high-contrast schemes; what assistive technology reads SHALL NOT change with it.
SNK-33 THE snackbar SHALL draw the action's tonal layer in every form and in every state of the action: the action's content color of that form and state, at comp.snackbar.action.tonal-layer.opacity, filling the action's container to its corners, above the container color of that state (SNK-2) and above a ripple that grows on it (SNK-6), and under the label. The layer is the action's alone, SHALL NOT take a press or change the action's size, and SHALL NOT be drawn WHERE comp.snackbar.action.tonal-layer.opacity is 0, where the action is its container alone. The label SHALL keep 4.5:1 against the action as drawn (the container color of the state with the layer over it) in every form, state and scheme of the default system, and 7:1 in its high-contrast schemes.
SNK-34 WHILE closable is true, and WHILE the expand button is drawn (SNK-23), THE snackbar SHALL draw that button, in the place SNK-15, SNK-16 and SNK-29 give it, as an icon-only Button of the small size showing a glyph of the system's icon set (close; expand_more or expand_less, SNK-23) and named by closeLabel or expandLabel, in the variant comp.snackbar.close-button.variant names and in the tone of the snackbar's form (BTN-45): the form's message color — comp.snackbar.supporting-text.color in the default form, and the token of that name under the form's prefix in every other (SNK-21, SNK-30) — with the form's focus-ring color (SNK-3) for its focus indicator, both given through the channel a Button reads from the component that places it, and otherwise unrestyled: it reads Button's own tokens for its size, glyph size, corners and state opacities, answers hover, focus and press as any Button does, and keeps its own minimum target (BTN-5). WHEN the snackbar's form changes while it is shown THE snackbar SHALL give the buttons the new form's tone with the new form's colors (SNK-30). THE snackbar SHALL draw no close or expand mark of its own (no path, shape or character of its own), and the expand button SHALL expose to assistive technology whether the text is expanded and which text it shows and hides (SNK-24).
SNK-35 WHILE placement is top-end THE host SHALL place the snackbar at the top of its area, at its end side (the right in a left-to-right layout and the left in a right-to-left one, taken from where the host is used as SNK-27 says), comp.snackbar.container.margin below the top edge and comp.snackbar.container.margin from the end edge, inside the safe area and below what the frame keeps over the top of the area (a sticky header's present height, APS-17), so that it never covers a top app bar; WHILE the area is narrower than comp.snackbar.breakpoint.medium (a compact window) it SHALL draw the snackbar across the top of the area instead, as wide as the area less comp.snackbar.container.margin on each side, within comp.snackbar.container.max-width. WHEN the product asks for a snackbar without a placement THE host SHALL give it the placement the host itself is given, and bottom where it is given none. The placement SHALL NOT change the snackbar's layer (SNK-13), its other size bounds (SNK-14), its place in the focus order (SNK-11) or its announcement (SNK-10), and the host SHALL still show one snackbar at a time, in order (SNK-12): at the top as at the bottom, a snackbar asked for while another shows waits for it to close, and no two are stacked.
SNK-36 WHILE placement is top-end, WHEN a snackbar is shown THE snackbar SHALL descend into place from above the top edge of its host by its own height plus its margin while fading in, over comp.snackbar.motion.enter-duration with comp.snackbar.motion.enter-easing, as SNK-17 rises at the bottom; WHEN it closes it SHALL fade out in place as SNK-17 says, and WHERE motion is reduced it SHALL fade in and out without moving, or appear and disappear at once where the platform removes every animation.
SNK-37 WHILE placement is top-end, WHEN the bar expands or collapses (SNK-24) THE snackbar SHALL grow downward and shrink back with its top edge in place, so that, its content being aligned to the top (SNK-29), nothing of its first row moves on the screen during the motion or after it; WHILE it is expanded it SHALL be at most as tall as SNK-25 allows, the area's height being measured from below what the frame keeps over its top (SNK-35).

プラットフォーム別の名前対応

パリティは API が同一であることを意味しません。同じ契約を、各プラットフォームの流儀で表現します。

仕様 Web SwiftUI Jetpack Compose Flutter React Native
コンポーネント tanrei-snackbar TanreiSnackbar TanreiSnackbar TanreiSnackbar Snackbar
message · プロパティ message message message message message
title · プロパティ headline title title title title
action · プロパティ action actionLabel actionLabel actionLabel actionLabel
status · プロパティ status status status status status
statusLabel · プロパティ statusLabel statusLabel statusLabel statusLabel statusLabel
inverse · プロパティ inverse inverse inverse inverse inverse
closable · プロパティ closable withCloseButton withDismissAction showCloseIcon closable
closeLabel · プロパティ closeLabel closeLabel closeLabel closeIconLabel closeLabel
expanded · プロパティ expanded isExpanded expanded expanded expanded
expandLabel · プロパティ expandLabel expandLabel expandLabel expandLabel expandLabel
duration · プロパティ duration duration duration duration duration
placement · プロパティ placement placement placement placement placement
action · イベント action TanreiSnackbarResult.action TanreiSnackbarResult.ActionPerformed TanreiSnackbarClosedReason.action onAction
dismiss · イベント dismiss TanreiSnackbarResult.dismissed TanreiSnackbarResult.Dismissed TanreiSnackbarClosedReason onDismiss
toggle · イベント expandedchange onExpandedChange onExpandedChange onExpandedChanged onExpandedChange
備考 title is the headline property and attribute, because HTML's global title attribute would show a tooltip over the whole bar (as the app bar's). closeLabel, statusLabel and expandLabel are the close-label, status-label and expand-label attributes. inverse is a property that is true, false or null (the default); the inverse attribute sets it: absent is null, present (empty or true) is true, and inverse="false" is false, the one way to refuse a system whose choice is on. While it is null the snackbar reads --tanrei-comp-snackbar-container-inverse from the cascade, which carries the choice's name (SNK-30). The opacities of SNK-21, SNK-31, SNK-32 and SNK-33 are applied to the color of the part they name (a mix of that color with transparent), never as an element's opacity, so nothing else fades with it; a status form's edge is a shadow layer of its own before the elevation's, and the tonal layer lies inside the action, under its label. The focus indicator's color is the form's focus-ring-color custom property, handed to the shared focus primitive (SNK-3). The expand and close buttons are <tanrei-button size="small"> elements in the snackbar's shadow root, given the variant --tanrei-comp-snackbar-close-button-variant names and the form's tone through the Button's internal channel (SNK-34); the expand button's expanded state and the text it controls are written on the Button's own native button (aria-expanded, and ariaControlsElements, since an id cannot be named across the Button's shadow root). dismiss is a CustomEvent whose detail.reason is the reason. toggle is the expandedchange CustomEvent, whose detail.expanded is the new value, because a popover element already fires a native toggle when it is shown and hidden. The host (<tanrei-snackbar-host>) queues and places snackbars; show(options) takes title, status, statusLabel, inverse and expandLabel as well; see the accessibility notes. placement is the placement attribute of <tanrei-snackbar> and of <tanrei-snackbar-host>: the host's is the placement of each snackbar whose show() options name none (SNK-35). What the frame keeps over the top of the area is --tanrei-app-shell-scroll-offset on the document's root, which <tanrei-app-shell> sets to its sticky header's height (APS-17) and a product's own frame may set; the safe area is env(safe-area-inset-*). A compact window is one narrower than --tanrei-comp-snackbar-breakpoint-medium, read by the stylesheet itself, with no listener. Snapshot harness: each fixture is a <tanrei-snackbar> rendered outside a host (SNK-19), on a 400 px wide stage; expanded-scrolls on a stage 400 × 240, and top-end and top-end-rtl on a stage 720 wide, wider than a compact window. Lagging: ships an earlier form of the snackbar: always an inverse bar, with a text action, no status forms, no title, no expansion and no inverse. A host is a @Observable TanreiSnackbarHost attached with .tanreiSnackbarHost(_:) on the view whose bottom edge it uses; await host.show(message:actionLabel:withCloseButton:closeLabel:duration:) returns a TanreiSnackbarResult (.action, or .dismissed(reason)) once the snackbar has closed. It is drawn in an overlay of that view, not in a sheet, which would be modal. Snapshot harness: TanreiSnackbar rendered in place (SNK-19) in a 400-point frame. inverse is a Bool?, nil by default. Not yet: the top-end placement (SNK-35, SNK-36, SNK-37); the expand and close buttons as small Buttons in the form's tone (SNK-34); the title's letter-spacing by the script of its text (SNK-22, PRS-20). Lagging: ships an earlier form of the snackbar: always an inverse bar, with a text action, no status forms, no title, no expansion and no inverse. Shaped like Material's snackbar API: TanreiSnackbarHost(hostState) is placed in the layout (for example a scaffold's snackbar slot), and hostState.showSnackbar(message, actionLabel, withDismissAction, closeLabel, duration) suspends until the snackbar has closed and returns the result; cancelling the coroutine closes it with the reason host. The recommended timeout is AccessibilityManager.calculateRecommendedTimeoutMillis. Snapshot harness: TanreiSnackbar(message = …) rendered in place (SNK-19) in a 400 dp wide box. inverse is a Boolean?, null by default. Not yet: the top-end placement (SNK-35, SNK-36, SNK-37); the expand and close buttons as small Buttons in the form's tone (SNK-34); the title's letter-spacing by the script of its text (SNK-22, PRS-20). Lagging: ships an earlier form of the snackbar: always an inverse bar, with a text action, no status forms, no title, no expansion and no inverse. Shaped like Flutter's ScaffoldMessenger: a TanreiSnackbarMessenger placed around the page, inside its route, hosts the queue (so a Tanrei dialog's route covers it and SNK-12's wait applies), and TanreiSnackbarMessenger.of(context).show(TanreiSnackbar(...)) returns a controller whose closed future completes with a TanreiSnackbarClosedReason (action, close, escape, timeout, host). showCloseIcon is Flutter's own name for the close button. A running screen reader is MediaQuery.accessibleNavigation (as Flutter's own SnackBar reads it); Android's recommended timeout needs a platform channel, since Flutter does not expose it. Snapshot harness: a golden of TanreiSnackbar rendered in place (SNK-19) in a 400-wide surface. inverse is a bool?, null by default. Not yet: the top-end placement (SNK-35, SNK-36, SNK-37); the expand and close buttons as small Buttons in the form's tone (SNK-34); the title's letter-spacing by the script of its text (SNK-22, PRS-20). Lagging: ships an earlier form of the snackbar: always an inverse bar, with a text action, no status forms, no title, no expansion and no inverse. TanreiProvider renders the host in its root overlay layer, not in a Modal (a Modal is modal and takes focus); useSnackbar().show({ message, actionLabel, closable, closeLabel, duration }) returns a promise of the result. A Snackbar element rendered directly is drawn in place (SNK-19). Snapshot harness: Snackbar rendered in place in a 400-wide view. inverse is boolean | null | undefined; both null and undefined take the system's choice. Not yet: the top-end placement (SNK-35, SNK-36, SNK-37); the expand and close buttons as small Buttons in the form's tone (SNK-34); the title's letter-spacing by the script of its text (SNK-22, PRS-20).

トークン

トークン 参照先 解決値(ライト)
--tanrei-comp-snackbar-action-container-color comp.snackbar.container.color #ffffff
--tanrei-comp-snackbar-action-content-color comp.snackbar.supporting-text.color #1b1b1b
--tanrei-comp-snackbar-action-error-container-color comp.snackbar.error.container.color #ffedeb
--tanrei-comp-snackbar-action-error-content-color comp.snackbar.error.supporting-text.color #3a211f
--tanrei-comp-snackbar-action-error-state-layer-color comp.snackbar.action.error.content.color #3a211f
--tanrei-comp-snackbar-action-focused-container-color — #e8e8e8
--tanrei-comp-snackbar-action-focused-content-color — #1b1b1b
--tanrei-comp-snackbar-action-focused-error-container-color — #ebd9d7
--tanrei-comp-snackbar-action-focused-error-content-color — #3a211f
--tanrei-comp-snackbar-action-focused-error-state-layer-color — #3a211f
--tanrei-comp-snackbar-action-focused-info-container-color — #d9dceb
--tanrei-comp-snackbar-action-focused-info-content-color — #212839
--tanrei-comp-snackbar-action-focused-info-state-layer-color — #212839
--tanrei-comp-snackbar-action-focused-inverse-error-container-color — #4a312f
--tanrei-comp-snackbar-action-focused-inverse-error-content-color — #ffe1df
--tanrei-comp-snackbar-action-focused-inverse-error-state-layer-color — #ffe1df
--tanrei-comp-snackbar-action-focused-inverse-info-container-color — #303748
--tanrei-comp-snackbar-action-focused-inverse-info-content-color — #e1e8ff
--tanrei-comp-snackbar-action-focused-inverse-info-state-layer-color — #e1e8ff
--tanrei-comp-snackbar-action-focused-inverse-success-container-color — #2b3b2b
--tanrei-comp-snackbar-action-focused-inverse-success-content-color — #daeed6
--tanrei-comp-snackbar-action-focused-inverse-success-state-layer-color — #daeed6
--tanrei-comp-snackbar-action-focused-inverse-warning-container-color — #47341f
--tanrei-comp-snackbar-action-focused-inverse-warning-content-color — #ffe3cb
--tanrei-comp-snackbar-action-focused-inverse-warning-state-layer-color — #ffe3cb
--tanrei-comp-snackbar-action-focused-inverse-container-color — #343434
--tanrei-comp-snackbar-action-focused-inverse-content-color — #f1f1f1
--tanrei-comp-snackbar-action-focused-inverse-state-layer-color — #f1f1f1
--tanrei-comp-snackbar-action-focused-state-layer-color — #1b1b1b
--tanrei-comp-snackbar-action-focused-success-container-color — #cee2cc
--tanrei-comp-snackbar-action-focused-success-content-color — #1b2c1c
--tanrei-comp-snackbar-action-focused-success-state-layer-color — #1b2c1c
--tanrei-comp-snackbar-action-focused-warning-container-color — #ebdacb
--tanrei-comp-snackbar-action-focused-warning-content-color — #362410
--tanrei-comp-snackbar-action-focused-warning-state-layer-color — #362410
--tanrei-comp-snackbar-action-hovered-container-color — #ededed
--tanrei-comp-snackbar-action-hovered-content-color — #1b1b1b
--tanrei-comp-snackbar-action-hovered-error-container-color — #efdddb
--tanrei-comp-snackbar-action-hovered-error-content-color — #3a211f
--tanrei-comp-snackbar-action-hovered-error-state-layer-color — #3a211f
--tanrei-comp-snackbar-action-hovered-info-container-color — #dde0ef
--tanrei-comp-snackbar-action-hovered-info-content-color — #212839
--tanrei-comp-snackbar-action-hovered-info-state-layer-color — #212839
--tanrei-comp-snackbar-action-hovered-inverse-error-container-color — #462d2b
--tanrei-comp-snackbar-action-hovered-inverse-error-content-color — #ffe1df
--tanrei-comp-snackbar-action-hovered-inverse-error-state-layer-color — #ffe1df
--tanrei-comp-snackbar-action-hovered-inverse-info-container-color — #2c3344
--tanrei-comp-snackbar-action-hovered-inverse-info-content-color — #e1e8ff
--tanrei-comp-snackbar-action-hovered-inverse-info-state-layer-color — #e1e8ff
--tanrei-comp-snackbar-action-hovered-inverse-success-container-color — #273727
--tanrei-comp-snackbar-action-hovered-inverse-success-content-color — #daeed6
--tanrei-comp-snackbar-action-hovered-inverse-success-state-layer-color — #daeed6
--tanrei-comp-snackbar-action-hovered-inverse-warning-container-color — #42301b
--tanrei-comp-snackbar-action-hovered-inverse-warning-content-color — #ffe3cb
--tanrei-comp-snackbar-action-hovered-inverse-warning-state-layer-color — #ffe3cb
--tanrei-comp-snackbar-action-hovered-inverse-container-color — #303030
--tanrei-comp-snackbar-action-hovered-inverse-content-color — #f1f1f1
--tanrei-comp-snackbar-action-hovered-inverse-state-layer-color — #f1f1f1
--tanrei-comp-snackbar-action-hovered-state-layer-color — #1b1b1b
--tanrei-comp-snackbar-action-hovered-success-container-color — #d2e6cf
--tanrei-comp-snackbar-action-hovered-success-content-color — #1b2c1c
--tanrei-comp-snackbar-action-hovered-success-state-layer-color — #1b2c1c
--tanrei-comp-snackbar-action-hovered-warning-container-color — #efdecf
--tanrei-comp-snackbar-action-hovered-warning-content-color — #362410
--tanrei-comp-snackbar-action-hovered-warning-state-layer-color — #362410
--tanrei-comp-snackbar-action-info-container-color comp.snackbar.info.container.color #edf0ff
--tanrei-comp-snackbar-action-info-content-color comp.snackbar.info.supporting-text.color #212839
--tanrei-comp-snackbar-action-info-state-layer-color comp.snackbar.action.info.content.color #212839
--tanrei-comp-snackbar-action-inverse-error-container-color comp.snackbar.inverse-error.container.color #361d1b
--tanrei-comp-snackbar-action-inverse-error-content-color comp.snackbar.inverse-error.supporting-text.color #ffe1df
--tanrei-comp-snackbar-action-inverse-error-state-layer-color comp.snackbar.action.inverse-error.content.color #ffe1df
--tanrei-comp-snackbar-action-inverse-info-container-color comp.snackbar.inverse-info.container.color #1c2334
--tanrei-comp-snackbar-action-inverse-info-content-color comp.snackbar.inverse-info.supporting-text.color #e1e8ff
--tanrei-comp-snackbar-action-inverse-info-state-layer-color comp.snackbar.action.inverse-info.content.color #e1e8ff
--tanrei-comp-snackbar-action-inverse-success-container-color comp.snackbar.inverse-success.container.color #172718
--tanrei-comp-snackbar-action-inverse-success-content-color comp.snackbar.inverse-success.supporting-text.color #daeed6
--tanrei-comp-snackbar-action-inverse-success-state-layer-color comp.snackbar.action.inverse-success.content.color #daeed6
--tanrei-comp-snackbar-action-inverse-warning-container-color comp.snackbar.inverse-warning.container.color #32200c
--tanrei-comp-snackbar-action-inverse-warning-content-color comp.snackbar.inverse-warning.supporting-text.color #ffe3cb
--tanrei-comp-snackbar-action-inverse-warning-state-layer-color comp.snackbar.action.inverse-warning.content.color #ffe3cb
--tanrei-comp-snackbar-action-inverse-container-color comp.snackbar.inverse.container.color #1f1f1f
--tanrei-comp-snackbar-action-inverse-content-color comp.snackbar.inverse.supporting-text.color #f1f1f1
--tanrei-comp-snackbar-action-inverse-state-layer-color comp.snackbar.action.inverse.content.color #f1f1f1
--tanrei-comp-snackbar-action-pressed-container-color — #e8e8e8
--tanrei-comp-snackbar-action-pressed-content-color — #1b1b1b
--tanrei-comp-snackbar-action-pressed-error-container-color — #ebd9d7
--tanrei-comp-snackbar-action-pressed-error-content-color — #3a211f
--tanrei-comp-snackbar-action-pressed-error-state-layer-color — #3a211f
--tanrei-comp-snackbar-action-pressed-info-container-color — #d9dceb
--tanrei-comp-snackbar-action-pressed-info-content-color — #212839
--tanrei-comp-snackbar-action-pressed-info-state-layer-color — #212839
--tanrei-comp-snackbar-action-pressed-inverse-error-container-color — #4a312f
--tanrei-comp-snackbar-action-pressed-inverse-error-content-color — #ffe1df
--tanrei-comp-snackbar-action-pressed-inverse-error-state-layer-color — #ffe1df
--tanrei-comp-snackbar-action-pressed-inverse-info-container-color — #303748
--tanrei-comp-snackbar-action-pressed-inverse-info-content-color — #e1e8ff
--tanrei-comp-snackbar-action-pressed-inverse-info-state-layer-color — #e1e8ff
--tanrei-comp-snackbar-action-pressed-inverse-success-container-color — #2b3b2b
--tanrei-comp-snackbar-action-pressed-inverse-success-content-color — #daeed6
--tanrei-comp-snackbar-action-pressed-inverse-success-state-layer-color — #daeed6
--tanrei-comp-snackbar-action-pressed-inverse-warning-container-color — #47341f
--tanrei-comp-snackbar-action-pressed-inverse-warning-content-color — #ffe3cb
--tanrei-comp-snackbar-action-pressed-inverse-warning-state-layer-color — #ffe3cb
--tanrei-comp-snackbar-action-pressed-inverse-container-color — #343434
--tanrei-comp-snackbar-action-pressed-inverse-content-color — #f1f1f1
--tanrei-comp-snackbar-action-pressed-inverse-state-layer-color — #f1f1f1
--tanrei-comp-snackbar-action-pressed-state-layer-color — #1b1b1b
--tanrei-comp-snackbar-action-pressed-success-container-color — #cee2cc
--tanrei-comp-snackbar-action-pressed-success-content-color — #1b2c1c
--tanrei-comp-snackbar-action-pressed-success-state-layer-color — #1b2c1c
--tanrei-comp-snackbar-action-pressed-warning-container-color — #ebdacb
--tanrei-comp-snackbar-action-pressed-warning-content-color — #362410
--tanrei-comp-snackbar-action-pressed-warning-state-layer-color — #362410
--tanrei-comp-snackbar-action-state-layer-color comp.snackbar.action.content.color #1b1b1b
--tanrei-comp-snackbar-action-success-container-color comp.snackbar.success.container.color #e2f6df
--tanrei-comp-snackbar-action-success-content-color comp.snackbar.success.supporting-text.color #1b2c1c
--tanrei-comp-snackbar-action-success-state-layer-color comp.snackbar.action.success.content.color #1b2c1c
--tanrei-comp-snackbar-action-tonal-layer-opacity — 0.12
--tanrei-comp-snackbar-action-warning-container-color comp.snackbar.warning.container.color #ffeee0
--tanrei-comp-snackbar-action-warning-content-color comp.snackbar.warning.supporting-text.color #362410
--tanrei-comp-snackbar-action-warning-state-layer-color comp.snackbar.action.warning.content.color #362410
--tanrei-comp-snackbar-breakpoint-medium sys.breakpoint.medium 600px
--tanrei-comp-snackbar-close-button-variant — 1
--tanrei-comp-snackbar-container-color sys.color.surface-bright #ffffff
--tanrei-comp-snackbar-container-elevation sys.elevation.level3 0px 0px 0px 1px rgb(0 0 0 / 0.08), 0px 1px 1px 0px rgb(0 0 0 / 0.04), 0px 4px 8px -4px rgb(0 0 0 / 0.04), 0px 16px 24px -8px rgb(0 0 0 / 0.06)
--tanrei-comp-snackbar-container-gap sys.space.space100 8px
--tanrei-comp-snackbar-container-inverse — 0
--tanrei-comp-snackbar-container-margin sys.space.space200 16px
--tanrei-comp-snackbar-container-max-width — 560px
--tanrei-comp-snackbar-container-min-height — 48px
--tanrei-comp-snackbar-container-min-width — 288px
--tanrei-comp-snackbar-container-padding-block sys.space.space150 12px
--tanrei-comp-snackbar-container-padding-inline-end sys.space.space200 16px
--tanrei-comp-snackbar-container-padding-inline-end-with-controls sys.space.space100 8px
--tanrei-comp-snackbar-container-padding-inline-start sys.space.space200 16px
--tanrei-comp-snackbar-container-shape sys.shape.corner.medium 8px
--tanrei-comp-snackbar-controls-gap sys.space.space50 4px
--tanrei-comp-snackbar-controls-height comp.button.container.height.small 32px
--tanrei-comp-snackbar-controls-padding-inline sys.space.space175 14px
--tanrei-comp-snackbar-controls-shape sys.shape.corner.full 9999px
--tanrei-comp-snackbar-error-container-color sys.color.error-container #ffedeb
--tanrei-comp-snackbar-error-focus-ring-color sys.color.on-error-container #3a211f
--tanrei-comp-snackbar-error-icon-color sys.color.error #cd262f
--tanrei-comp-snackbar-error-outline-color sys.color.error #cd262f
--tanrei-comp-snackbar-error-supporting-text-color sys.color.on-error-container #3a211f
--tanrei-comp-snackbar-error-title-color comp.snackbar.error.supporting-text.color #3a211f
--tanrei-comp-snackbar-focus-ring-color sys.color.focus-ring #0070ac
--tanrei-comp-snackbar-focused-opacity — 1
--tanrei-comp-snackbar-hovered-opacity — 1
--tanrei-comp-snackbar-icon-gap sys.space.space150 12px
--tanrei-comp-snackbar-icon-opacity — 0.85
--tanrei-comp-snackbar-icon-size sys.icon.medium 24px
--tanrei-comp-snackbar-info-container-color sys.color.info-container #edf0ff
--tanrei-comp-snackbar-info-focus-ring-color sys.color.on-info-container #212839
--tanrei-comp-snackbar-info-icon-color sys.color.info #3066cb
--tanrei-comp-snackbar-info-outline-color sys.color.info #3066cb
--tanrei-comp-snackbar-info-supporting-text-color sys.color.on-info-container #212839
--tanrei-comp-snackbar-info-title-color comp.snackbar.info.supporting-text.color #212839
--tanrei-comp-snackbar-inverse-error-container-color sys.color.on-error-container #361d1b
--tanrei-comp-snackbar-inverse-error-focus-ring-color comp.snackbar.inverse-error.supporting-text.color #ffe1df
--tanrei-comp-snackbar-inverse-error-icon-color comp.snackbar.inverse-error.supporting-text.color #ff928c
--tanrei-comp-snackbar-inverse-error-outline-color sys.color.error #ff928c
--tanrei-comp-snackbar-inverse-error-supporting-text-color sys.color.error-container #ffe1df
--tanrei-comp-snackbar-inverse-error-title-color comp.snackbar.inverse-error.supporting-text.color #ffe1df
--tanrei-comp-snackbar-inverse-info-container-color sys.color.on-info-container #1c2334
--tanrei-comp-snackbar-inverse-info-focus-ring-color comp.snackbar.inverse-info.supporting-text.color #e1e8ff
--tanrei-comp-snackbar-inverse-info-icon-color comp.snackbar.inverse-info.supporting-text.color #8eafff
--tanrei-comp-snackbar-inverse-info-outline-color sys.color.info #8eafff
--tanrei-comp-snackbar-inverse-info-supporting-text-color sys.color.info-container #e1e8ff
--tanrei-comp-snackbar-inverse-info-title-color comp.snackbar.inverse-info.supporting-text.color #e1e8ff
--tanrei-comp-snackbar-inverse-success-container-color sys.color.on-success-container #172718
--tanrei-comp-snackbar-inverse-success-focus-ring-color comp.snackbar.inverse-success.supporting-text.color #daeed6
--tanrei-comp-snackbar-inverse-success-icon-color comp.snackbar.inverse-success.supporting-text.color #62c572
--tanrei-comp-snackbar-inverse-success-outline-color sys.color.success #62c572
--tanrei-comp-snackbar-inverse-success-supporting-text-color sys.color.success-container #daeed6
--tanrei-comp-snackbar-inverse-success-title-color comp.snackbar.inverse-success.supporting-text.color #daeed6
--tanrei-comp-snackbar-inverse-warning-container-color sys.color.on-warning-container #32200c
--tanrei-comp-snackbar-inverse-warning-focus-ring-color comp.snackbar.inverse-warning.supporting-text.color #ffe3cb
--tanrei-comp-snackbar-inverse-warning-icon-color comp.snackbar.inverse-warning.supporting-text.color #f39e3a
--tanrei-comp-snackbar-inverse-warning-outline-color sys.color.warning #f39e3a
--tanrei-comp-snackbar-inverse-warning-supporting-text-color sys.color.warning-container #ffe3cb
--tanrei-comp-snackbar-inverse-warning-title-color comp.snackbar.inverse-warning.supporting-text.color #ffe3cb
--tanrei-comp-snackbar-inverse-container-color sys.color.inverse-surface #1f1f1f
--tanrei-comp-snackbar-inverse-focus-ring-color sys.color.inverse-on-surface #f1f1f1
--tanrei-comp-snackbar-inverse-supporting-text-color sys.color.inverse-on-surface #f1f1f1
--tanrei-comp-snackbar-inverse-title-color comp.snackbar.inverse.supporting-text.color #f1f1f1
--tanrei-comp-snackbar-motion-enter-duration sys.motion.role.appear.toast.enter 350ms
--tanrei-comp-snackbar-motion-enter-easing sys.motion.easing.enter cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-snackbar-motion-exit-duration sys.motion.role.appear.toast.exit 300ms
--tanrei-comp-snackbar-motion-exit-easing sys.motion.easing.exit cubic-bezier(0.3, 0, 1, 1)
--tanrei-comp-snackbar-motion-expand-duration sys.motion.role.resize.large 400ms
--tanrei-comp-snackbar-motion-expand-easing sys.motion.easing.spatial cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-snackbar-motion-feedback-duration sys.motion.role.feedback 450ms
--tanrei-comp-snackbar-motion-state-duration sys.motion.role.state.default 250ms
--tanrei-comp-snackbar-motion-state-easing sys.motion.easing.state cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-snackbar-outline-opacity — 0.4
--tanrei-comp-snackbar-outline-width sys.border.hairline 1px
--tanrei-comp-snackbar-pressed-opacity — 1
--tanrei-comp-snackbar-success-container-color sys.color.success-container #e2f6df
--tanrei-comp-snackbar-success-focus-ring-color sys.color.on-success-container #1b2c1c
--tanrei-comp-snackbar-success-icon-color sys.color.success #077b32
--tanrei-comp-snackbar-success-outline-color sys.color.success #077b32
--tanrei-comp-snackbar-success-supporting-text-color sys.color.on-success-container #1b2c1c
--tanrei-comp-snackbar-success-title-color comp.snackbar.success.supporting-text.color #1b2c1c
--tanrei-comp-snackbar-supporting-text-color sys.color.on-surface #1b1b1b
--tanrei-comp-snackbar-supporting-text-max-lines — 3
--tanrei-comp-snackbar-supporting-text-under-title-opacity — 0.8
--tanrei-comp-snackbar-timeout-long — 10000ms
--tanrei-comp-snackbar-timeout-short — 4000ms
--tanrei-comp-snackbar-title-color comp.snackbar.supporting-text.color #1b1b1b
--tanrei-comp-snackbar-title-gap sys.space.space50 4px
--tanrei-comp-snackbar-title-max-lines — 3
--tanrei-comp-snackbar-warning-container-color sys.color.warning-container #ffeee0
--tanrei-comp-snackbar-warning-focus-ring-color sys.color.on-warning-container #362410
--tanrei-comp-snackbar-warning-icon-color sys.color.warning #9b5c00
--tanrei-comp-snackbar-warning-outline-color sys.color.warning #9b5c00
--tanrei-comp-snackbar-warning-supporting-text-color sys.color.on-warning-container #362410
--tanrei-comp-snackbar-warning-title-color comp.snackbar.warning.supporting-text.color #362410
--tanrei-comp-snackbar-controls-label-typescale sys.typescale.label 500 14px/1.4286
--tanrei-comp-snackbar-supporting-text-typescale sys.typescale.body 400 14px/1.5714
--tanrei-comp-snackbar-title-typescale sys.typescale.heading-6 500 14px/1.4286

フィクスチャ

基準となる描画パターンです。全プラットフォームが同じ ID でスナップショットを撮り、結果を比較できるようにしています。

生成元 specs/snackbar.spec.json, tokens/comp/snackbar.tokens.json, design/tokens/

ガイドライン

使う場面

専用の画面を持たずに終わった操作について、その人がいま行ったことを確かめられるように使います。「会話をアーカイブしました」「リンクをコピーしました」「写真を保存しました」などです。それを取り消すか、続けて行うアクションを 1 つまで添えられます。

スナックバーは報告するもので、問いかけるものではありません。2 つのアクションからの選択、作業を止めてしまうエラー、確認してもらう必要のあることは、ダイアログか、関係する場所のすぐ横に置きます。エラーの種類は、「メッセージを送信できませんでした」と「再試行」のように、あとから戻って対処できる失敗に使います。

スナックバーかダイアログか

どちらもページの上に現れますが、役割は正反対です。

観点 スナックバー ダイアログ
役割 すでに起きたことを報告します。 作業を先へ進める前に、判断や確認を求めます。
フォーカス 動きません。その人は作業を続けられます。 ダイアログの中へ移ります。後ろのページは操作できません。
残る時間 数秒、またはアクションか閉じるボタンが使われるまで。 その人が答えるまで。
アクション 1 つまで。なくてもメッセージは完結しています。 問いかけへの答えです。

メッセージを無視しても困ることが起きないなら、スナックバーです。答えがなければアプリが先へ進めないなら、ダイアログです。フィールドの入力の誤りのように、画面の一部分に属していて、見え続けている必要のあるメッセージは、どちらでもなく、その場所に置きます。たとえばフィールドの補足テキストです。

構成要素

要素 設定 補足
バー inverse 既定ではスキームの一段高いサーフェス、または反対のスキームの色です。サーフェスの上か、反転かを参照してください。
状態のグリフ status と statusLabel 省略できます。状態の種類では、文章の前にその状態のグリフを描きます。状態の種類を参照してください。
タイトル title(Web では headline) 省略できます。メッセージの上に置く短い見出しで、メッセージと同じ大きさの太めの文字です。なければメッセージだけになります。
メッセージ message 必須です。1〜2 文の短いプレーンテキストで、全文が読み上げられます。タイトルがあるときは、その説明になり、タイトルより少し控えめに描きます。
アクション action(ラベル) 省略できます。「元に戻す」「表示」「再試行」など。tonal のボタンとして、バーから控えめに一段だけ離れたピルで描き、ラベルはバーの文章の色です。Button としてではなく、スナックバー自身のトークンで描き、横に並ぶ small の Button と同じ高さにします。タイトルとメッセージの行を合わせて、その横で 3 行以上になるときや、ラベル自体が折り返すときは、次の行に回ります。
展開ボタン expandLabel、expanded アイコンだけの small の Button で、閉じるボタンと同じように描きます。タイトルかメッセージが 3 行を超える間だけ描きます。expandLabel は「メッセージ全体を表示」のようなアクセシブルネームです。
閉じるボタン closable と closeLabel 省略できます。アイコンだけの small の Button です。閉じるまで残すメッセージや、早めに消したいこともあるメッセージに付けます。closeLabel はプロダクトの言語で書くアクセシブルネームです。

アクション、展開ボタン、閉じるボタンは、文章の 1 行目の横に並び、3 つとも small の Button と同じ高さです。

展開ボタンと閉じるボタン

展開ボタンと閉じるボタンは、アイコンだけの small の Button で、アイコンセットの expand_more、expand_less、close のグリフを使います。Tanrei のすべての閉じるボタンと同じく tonal で、色はバーから取ります。グリフはバーの文章の色で、その下にその色を半透明にした円を敷きます。そのため、サーフェス、状態の色合い、反転のどのバーの上でも操作として読め、バーにない色を持ち込むこともありません。ホバー、フォーカス、押下は Button のもので、同じ色の上に重なります。フォーカスリングはバー自身のフォーカスの色です。

プロダクトは、これをグリフだけで描くこともできます。変えるときは、デザインシステムのレイヤで comp.snackbar.close-button.variant を text の variant にし、すべてのスナックバーに一度に適用します。Material と Cupertino のプリセットは、元のデザインにあるグリフだけの見た目を保つために、まさにこの方法を使っています。

アクション

アクションは、バーになじむ tonal のボタンです。コンテナはバーと同じ色で、その上にラベルの色をごく薄く重ねます。そのため、サーフェスの上でも反転でも、状態の種類があってもなくても、バーからの離れ方は同じ控えめな一段です。ラベルはバーの文章の色です。

tonal なのは、たいてい「元に戻す」だからです。いま間違えた人が、ボタンがあることにすぐ気づけなければなりません。ラベルの文字だけでは、バーの上で見落とされがちです。

アクションを filled のボタンにしないでください。 filled のボタンが必要に思えるほど重要なアクションは、スナックバーに置くものではありません。スナックバーは消えてしまい、フォーカス順でも最後に届くからです。代わりに、画面のメインアクションから開くボトムシートに置きます。

アクションの見た目は、プロダクトが変えることもできます。変えるときは、デザインシステムのレイヤでアクションの色のトークンと tonal の重ねの不透明度に別の値を与え、すべてのスナックバーに一度に適用します。スナックバーごとの variant はありません。Material と Cupertino のプリセットは、元のデザインにあるテキストのアクションを保つために、まさにこの方法を使っています。

状態の種類

status グリフ 読み上げ 用途
default なし 控えめに ほとんどのメッセージ。アプリがいま行ったこと。
info info 控えめに その人の操作の結果ではないが、知っておく価値のあること。
success check_circle 控えめに その人が待っていた作業が、うまく終わったこと。
warning warning 割り込んで その人が対処しないと、うまくいかなくなりそうなこと。
error error 割り込んで あとから戻って対処できる失敗。再試行や説明のアクションを添えます。

状態の種類:info、success、warning、error

状態の種類では、バーをその状態の淡い色合いで描き、周りにその状態の色の細い線の縁を、文章の前にその色のグリフを描きます。縁は場所を取りません。default には独自の縁がなく、エレベーションが描く縁だけです。状態の種類を色だけで伝えることはありません。グリフの形で種類を見分けられ、statusLabel がプロダクトの言語で状態を言葉にします(「エラー」など)。このラベルはタイトルとメッセージより先に読み上げられます。warning と error は読み上げ中の内容に割り込み、ほかの種類は読み上げが終わるのを待ちます。

サーフェスの上か、反転か

既定では、スナックバーをスキームの一段高いサーフェスの上に描きます。ダイアログと同じ、エレベーションで浮かせた明るいサーフェスで、文章はスキームの文字色です。反転して描くと、反対のスキームの色(ライトのスキームでは暗いバー)になり、どこに現れても周りから際立ちます。

反転して描いたもの:default と 4 つの状態の種類

inverse 描き方
未設定(null、既定) デザインシステムの選択に従います。既定のシステムはサーフェスを、m3-like プリセットは反転を選んでいます。
true 反転して描きます。
false サーフェスの上に描きます。反転を選んでいるシステムでも同じです。

Web では、属性で 3 つのどれかを指定します。属性を書かなければ未設定、inverse は true、inverse="false" は false です。show() もオプションとして inverse を受け取ります。

反転して描いた状態の種類は、もう一方のテーマでのその状態の種類と、要素の 1 つ 1 つまで同じです。ライトテーマで反転した info のバーは、ダークテーマが描く info のバーであり、文章、グリフ、縁、アクションもそのテーマのものです。ダークテーマではライトテーマのものになります。どのコントラストレベルでも同じです。そのため、反転した状態の種類は、プロダクトがほかの場所で描いていない色を使わず、グリフと縁はサーフェスの上と同じくらい鮮やかです。

反転で変わるのは色だけです。大きさ、角、エレベーション、レイアウト、グリフ、動き、時間、読み上げ、キー操作は変わりません。反転は見た目であって、意味ではありません。メッセージが重要であることを伝えるために使わないでください。それを伝えるのは、状態の種類と statusLabel です。反転するかどうかは、プロダクト全体で一度だけ、デザインシステムのレイヤで決めることをおすすめします。そうすれば、どのスナックバーも同じ種類のメッセージに見えます。

長い文章

折りたたまれている間、タイトルとメッセージはそれぞれ 3 行までを表示し、切れたところを省略記号で終えます。そのとき、アクションと閉じるボタンの間に展開ボタンが現れます。展開ボタンを実行すると文章全体を表示し、新しい expanded の値とともに toggle(Web では expandedchange)を発火します。フォーカスはボタンに残り、スナックバーは閉じません。バーは下端を保ったまま上へ広がり(上端の終わり側に置いたときは、上端を保ったまま下へ広がり)、動きを減らす設定では瞬時に広がります。中身は上に揃えているので、バーが広がったり縮んだりする間も、グリフ、1 行目、その横のコントロールは上端からの距離を保ち、次の行に回ったアクションは下端からの距離を保ちます。コードから expanded を設定することもでき、そのときは何も発火しません。

展開しても、ホストの使える高さに収まらないときは、バーがその高さになり、文章がバーの中でスクロールします。グリフとコントロールは動きません。文章が切れていてもいなくても、支援技術には常に全文が届きます。

配置

placement バーの位置 使う場面
bottom(既定) ウィンドウの下端の中央。下端から余白を空けます。 ほとんどのプロダクトと、電話で使うすべてのプロダクト。
top-end ウィンドウの上端の終わり側。右上、右から左に書くレイアウトでは左上です。compact のウィンドウ(既定のシステムでは幅 600 未満)では、代わりに上端に幅いっぱいで置きます。 業務アプリケーションのデスクトップの画面。画面の下端が作業の場所になっているもの。

配置はプロダクト全体で 1 つに決めてホストに渡し、メッセージの出る場所を人が覚えられるようにします。<tanrei-snackbar-host> の placement は、そのホストが表示するすべてのスナックバーに効き、自分の placement を指定して頼んだスナックバーはそれを保ちます。電話では、Material がスナックバーを置く場所であり親指の届く場所でもある下端のままにします。ダッシュボード、管理画面、自前のツールバーやステータスバーや表のページ送りが下端にあるエディタなど、業務アプリケーションのデスクトップの画面では、Carbon と Fluent が通知を置く top-end を使います。

上端の終わり側では、バーは上端と終わり側の端から余白を空けて、セーフエリアの内側、固定されたヘッダーの下に置かれるので、上部のアプリバーを覆いません。Web では、ヘッダーの高さを --tanrei-app-shell-scroll-offset から読みます。この値は アプリシェル がドキュメントのルートに設定します。独自の枠組みを持つプロダクトは、自分の固定ヘッダーの高さをこのプロパティに設定します。登場するときは、下端の下から浮き上がる代わりに上端の上から下りてきて、展開したバーは下へ広がります。ほかは何も変わりません。表示は 1 度に 1 つで積み重ねることはなく、コントロールにはフォーカス順序のホストの位置で届き、読み上げ方も同じです。

<!-- このホストが表示するスナックバーは、すべて上端の終わり側に置かれます。 -->
<tanrei-snackbar-host placement="top-end"></tanrei-snackbar-host>

<script type="module">
  const snackbars = document.querySelector('tanrei-snackbar-host');
  snackbars.show({ message: 'Report exported', action: 'Open' });
  // 下端を指定して頼んだスナックバーは、自分の配置を保ちます。
  snackbars.show({ message: 'Photo saved', placement: 'bottom' });
</script>
<SnackbarHost ref={snackbars} placement="top-end" />

SwiftUI、Compose、Flutter、React Native には、上端の終わり側の配置がまだありません。スナックバーは下端に置かれます。

表示時間

duration 自分から閉じるまでの時間
auto(既定) アクションがなければ short。アクションがあれば時間切れにならないので、誰も操作の途中で締め出されません(WCAG 2.2.1)。
short 短い表示時間(既定のシステムでは 4 秒)で閉じます。
long 長い表示時間(既定のシステムでは 10 秒)で閉じます。
indefinite 人かコードが閉じるまで残ります。

時間で閉じるスナックバーは、ポインターが上にある間、フォーカスが中にある間、展開している間、アプリが隠れているかバックグラウンドにある間、Tanrei のモーダルダイアログに覆われている間は止まり、再開するときは最初から数え直します。Android のアクセシビリティのタイムアウトのほうが長ければそちらを使い、スクリーンリーダーが動いている間はアクション付きのスナックバーが時間切れになることはありません。アクション付きのスナックバーを時間で閉じるなら、メニューの「元に戻す」のように、同じアクションをほかの場所にも用意します。デバッグビルドがそのことを知らせます。

表示、フォーカス、閉じる

スナックバーは読むためのオーバーレイです。人はメッセージを受け取るだけなので、表示してもフォーカスは動きません。

  • 一度に 1 つ。 ホストは頼まれた順にスナックバーを表示します。次のものは、前のものがフェードアウトし終えるまで待ちます。Tanrei のモーダルダイアログが開いている間に頼まれたものは、ダイアログが閉じるまで待ちます。
  • 配置。 下端の中央に、下端と左右から余白を空けて、セーフエリアの内側、オンスクリーンキーボードより上に置きます。placement="top-end" のときは、上端の終わり側の、固定されたヘッダーの下に置きます(配置を参照)。どちらの場合も、領域のコンテンツより上、モーダルダイアログとそのスクリムより下に描きます。
  • 読み上げ。 現れたときに、支援技術が状態のラベル、タイトル、メッセージを 1 回読み上げ、続けてアクションのラベルを読みます。ふだんは控えめに、読み上げ中の内容の後に、warning と error では割り込んで読み上げます。キーボードのフォーカスもスクリーンリーダーのフォーカスも動かしません。
  • フォーカス。 アクション、展開ボタン、閉じるボタンには、フォーカス順でのホストの位置で Tab で届きます。ホストを最後に置くのはこのためです。バー自体は Tab で止まる位置ではありません。ただし、展開したバーの文章がスクロールする間は、その文章が止まる位置になります。
  • 閉じる。 アクションが実行されると閉じ(action)、そうでないときは次の理由とともに閉じます(dismiss)。
理由 いつ
close 閉じるボタンが実行されたとき。
escape フォーカスがスナックバーの中にあるときに Escape が押されたとき。
timeout short か long のスナックバーの時間が切れたとき。
host コードが閉じたとき、またはキューを空にしたとき。
  • フォーカスが戻る。 フォーカスが中にあるときにスナックバーが閉じると、フォーカスはスナックバーに入る前の要素へ戻ります。
  • 動き。 下端の下から浮き上がりながら(上端の終わり側では、上端の上から下りてきながら)フェードインし、その場でフェードアウトします。動きを減らす設定では動かずにフェードし、すべてのアニメーションを取り除く Android の設定では瞬時に表示と非表示を切り替えます。

推奨

  • 何が起きたかを過去形で書きます。「メッセージを送信しました」「3 つのファイルを移動しました」。
  • アクションには、そのメッセージの後に期待される短い動詞を使います。「元に戻す」「表示」「再試行」。
  • 起きたことに合う状態の種類を選び、状態の種類には必ず statusLabel を付けます。
  • スナックバーをサーフェスの上に描くか反転して描くか、どこに置くかは、プロダクト全体で一度だけ決めます。
  • 展開ボタンがめったに要らないくらい、文章を短く保ちます。
  • 理由がない限り、既定の auto のままにします。アクションの途中で誰かを締め出すことがありません。
  • ホストはページやレイアウトの最後に置き、そのコントロールがフォーカス順で最後になるようにします。

非推奨

  • 作業を止めるエラーや問いかけにスナックバーを使わない。ダイアログを使います。
  • アクションを 2 つ入れない。入るのは 1 つだけです。
  • アクションを filled のボタンにしない。filled が必要に思えるアクションをスナックバーに置かない。メインアクションから開くボトムシートを使います。
  • 日常的な確認に warning や error を使わない。スクリーンリーダーの読み上げに割り込みます。
  • 重要であることを示すために、スナックバーを反転して描かない。反転で変わるのは色だけです。どんな種類のメッセージかは、状態の種類が伝えます。
  • 画面ですでにはっきり分かることを、スナックバーで知らせない。
  • スナックバーやそのアクションにフォーカスを移さない。人を作業から引き離してはいけません。

アクセシビリティ

ロール
status
ラベル付け
The snackbar is a live region: polite in the default, info and success forms (the status role on the web) and assertive in the warning and error forms (the alert role on the web). When it appears it announces, once, statusLabel, the title, the whole message and the action's label. The status glyph is hidden from assistive technology; statusLabel gives the status in words. The action's name is its label; the close button's name is closeLabel; the expand button's name is expandLabel, and it exposes whether the text is expanded. The bar itself is not a tab stop; its text is one only while it scrolls.
最小ターゲット
見た目のサイズが小さくても、ヒット領域は最小ターゲットサイズを満たします。 minimum 32px · pointer 44px · spacing 8px · touch 48px

キーボード操作

キー 動作
Tab Reaches, at the host's place in the focus order, the text while it scrolls, then the action, the expand button and the close button. Showing a snackbar never moves focus.
Enter / Space Activates the focused action, expand button or close button.
Arrow keys, Page Up, Page Down While the expanded text scrolls and has focus, scroll it.
Escape While focus is inside the snackbar, closes it (dismiss, reason escape).

備考

  • WCAG 2.2.1 (timing adjustable): by default (auto) a snackbar with an action never times out. A product may time one out (short or long), but then the action must also be available elsewhere (Undo in a menu or a history), because a person who is slow, zoomed-in or using a screen reader may not reach it in time. The timeout pauses while the snackbar is hovered, focused or expanded, and Android's accessibility timeout lengthens it.
  • A snackbar reports; it does not ask. A choice between two actions, an error that blocks the person's work, or anything they must acknowledge belongs in a dialog or next to what it is about. The error form is for a failure the person can come back to (a message not sent, with Retry).
  • The action is a tonal button so that an undo is seen at once: its label is the strongest thing in it (at least 9.1:1 on the action at rest and 6.9:1 in its weakest state, in every form and scheme of the default system), and the action is one quiet step off its bar, the same step in every form (1.25–1.45:1 against the bar in all ten forms and six schemes), so it never outweighs the message and never weighs more on one status than on another. A button with a visible label needs no boundary contrast (WCAG 1.4.11). An action important enough to seem to want a filled button does not belong in a snackbar: put a Main Action Button in a bottom sheet instead.
  • WCAG 2.4.13 and 1.4.11 (focus appearance, non-text contrast): the focus indicator is drawn on the bar, so its color is the form's (SNK-3). On the default form's surface it is the system's focus color, which the build holds against every surface role; on a status bar and on an inverse bar it is that bar's content color, which the build holds against the bar at 4.5:1 or more. The system's focus color alone is held against no status bar and falls under 3:1 on the inverse surface in five of the six schemes.
  • Drawn inverse or not, a snackbar is the same message: the role, the announcement, the focus order and the timing do not change with its colors (SNK-30). A product should not use inverse to mean something (an inverse bar for important messages): that is what the status forms and statusLabel are for.
  • WCAG 1.4.1 (use of color): a status form is never told by color alone. It draws the status's glyph, a shape of the icon set, and gives statusLabel, the status in words, to assistive technology (SNK-21).
  • WCAG 1.4.3 and 1.4.11 (contrast): three parts are drawn at an opacity of their own color, so their contrast is that of the color as drawn, not of the role. In the default system the message under a title keeps at least 7.1:1 on its bar (7.6:1 in the high-contrast schemes) and the status glyph at least 3.6:1, in every form and scheme (SNK-32, SNK-21); a status form's edge is decoration beside the glyph and statusLabel and is held to nothing (SNK-31). A system that changes one of these opacities, or the colors under them, must keep 4.5:1 for the message and 3:1 for the glyph.
  • WCAG 1.4.11 (non-text contrast): the expand and close buttons are Buttons in the form's tone (SNK-34): the glyph is the bar's message color, and the circle under it is that color at comp.button.tone.container.opacity over the bar. In the default system the glyph keeps at least 8.0:1 against its circle at rest and 6.2:1 in its weakest states (focused and pressed), in all ten forms and six schemes — well above the 3:1 a glyph without a label needs. A system that changes the tone's opacity, the state-layer opacities or a form's colors must keep 3:1 for the glyph against the circle in every state.
  • Text size never hides text for good: a collapsed title or message ends with an ellipsis at its line limit and the expand button shows the rest (SNK-23); the action moves to its own row (SNK-16); an expanded bar taller than the window scrolls inside it (SNK-25).
  • Web: `<tanrei-snackbar-host>` is placed by the product, normally as the last element of the page (its place in the focus order). It shows `<tanrei-snackbar>` elements as `popover="manual"`, so they are in the top layer, and exposes `show(options): Promise<{ result: 'action' | 'dismiss', reason? }>` (whose options include `inverse`), `dismissCurrent()` and `clear()`. The live regions are the host's two, which are always in the document, so screen readers register them before the first message is inserted: one with the status role (polite) for the default, info and success forms, one with the alert role (assertive) for warning and error. The expand button carries `aria-expanded` and `aria-controls` naming the text.
  • SwiftUI, Compose, Flutter and React Native announce with the platform's own call, at default priority for the polite forms and at high priority for warning and error: AccessibilityNotification.Announcement with the speech announcement priority, a polite or assertive liveRegion, SemanticsService.announce with its assertiveness, and AccessibilityInfo.announceForAccessibility (announceForAccessibilityWithOptions with queue off for assertive on iOS). None of them takes focus. The expand button's state is the platform's expanded/collapsed semantics (Compose's expand and collapse actions, Flutter's expanded flag, React Native's accessibilityState.expanded, and on Apple platforms the expanded and collapsed value VoiceOver reads for a disclosure).
  • How a state looks is a value decision of the active design system; components read the state's tokens and never compute the effect themselves.

生成元 specs/snackbar.spec.json, tokens/sys/measure.tokens.json

コンポーネントが担うこと

  • スナックバーはライブリージョンで読み上げます。default、info、success は控えめなライブリージョン(Web では status ロール)、warning と error は割り込むライブリージョン(alert ロール)です。現れたときに、状態のラベル、タイトル、メッセージ、続いてアクションのラベルを 1 回読み上げます。文章が切れていても、読み上げるのは常に全文です。SwiftUI、Compose、Flutter、React Native ではプラットフォームの読み上げの仕組みを使い、warning と error では優先度を上げます。どれもフォーカスを奪いません。
  • 状態の種類を色だけで伝えることはありません。状態のグリフを描き、statusLabel をタイトルの前のテキストとして伝えます。グリフ自体は支援技術から隠し、展開と閉じるのグリフも同じく隠します。
  • アクションはラベルを、展開ボタンは expandLabel を、閉じるボタンは closeLabel を名前とします。どれもフォーカス順でのホストの位置で Tab で届き、Enter か Space で実行できます。
  • 展開ボタンは、展開しているかどうかを伝え、どちらの状態でも名前は変わりません。展開したバーの文章がスクロールするときは、文章はフォーカスが止まる位置になり、タイトルを名前とし、矢印キーと Page Up・Page Down でスクロールできます。
  • フォーカスが中にあるときに Escape を押すと閉じます。フォーカスが中にあるまま閉じると、フォーカスは元の場所へ戻ります。
  • バーの位置で変わるのは位置だけです。上端の終わり側のスナックバーにも、フォーカス順序のホストの位置で Tab で届き、下端のものとまったく同じように読み上げられ、固定されたヘッダーを覆うこともありません。
  • 既定では、アクション付きのスナックバーは時間切れになりません。時間で閉じるものは、ホバー、フォーカス、展開の間は止まり、Android のアクセシビリティのタイムアウトまで延び、アクション付きならスクリーンリーダーが動いている間は時間切れになりません。
  • アクションは、描く大きさを変えずに、縦横ともに最小ターゲットを満たします。展開ボタンと閉じるボタンは Button なので、Button 自身の最小ターゲットを保ちます。どのフォーカスリングもキーボードフォーカスのときだけ、スナックバーの種類に合わせた色で表示します。サーフェス、状態の色合い、反転のどのバーの上でも見えます。
  • 展開ボタンと閉じるボタンのグリフは、すべての状態と種類で、下の円に対して 3:1 以上を保ちます。既定のシステムでは、静止時に 8.0:1 以上、フォーカスと押下のときに 6.2:1 以上です。
  • 控えめに描く要素も、既定のシステムのすべてのスキームで、バーに対するコントラストを保ちます。状態のグリフは 3:1 以上、タイトルの下のメッセージは 4.5:1 以上、高コントラストのスキームでは 7:1 以上です。控えめにするのは色だけで、支援技術が読む内容は変わりません。反転して描いても同じです。
  • 文字を大きくしても、文章が見えなくなったままになることはありません。折りたたんだタイトルやメッセージは 3 行で省略記号で終わり、展開ボタンで残りを表示できます。アクションは次の行に回り、展開したバーが画面より高ければ中でスクロールします。
  • アイコンフォントが読み込まれる前に、グリフの名前がテキストとして描かれることはありません。フォントが描けるようになるまで、グリフの場所は空のままです。
  • 空のメッセージ、closeLabel のない閉じるボタン、statusLabel のない状態の種類、expandLabel のない展開ボタン、時間で閉じるアクション付きのスナックバーは、デバッグビルドで報告します。

プラットフォームごとの既知の制約:

  • Web:ブラウザーはスクリーンリーダーが動いているかを伝えず、アクセシビリティのタイムアウトもないので、時間で閉じるスナックバーをどちらによっても延ばしません。アクションに確実に届くようにしているのは、既定の auto の表示時間です。
  • Web:オンスクリーンキーボードを避けることは、キーボードのある実機ではまだ確かめていません。
  • SwiftUI は、フォーカスを前にあった要素へ戻せません。フォーカスが中にあるままスナックバーが閉じると、取り除かれたビューに対して SwiftUI が移す先へフォーカスが移ります。利用者側の NSHostingView のように、シーンの外で表示しているビューのホストでは、タイムアウトが進みません。
  • Compose は、フォーカスがスナックバーに入る前にどの要素にあったかを知ることができません。フォーカスを持ったままスナックバーが閉じると、フォーカスは入ってきた方向へ戻ります。Tab で入った場合は、ホストの前の要素です。フォーカスが中にある間にコードが閉じたときは、フォーカスは動きません。
  • Compose:Android のアクセシビリティのタイムアウトまで延ばすことと、ウィンドウが隠れている間に止めることは、まだテストで確かめていません。
  • Compose:控えめな読み上げ、ウィンドウにフォーカスがない間の一時停止、アクセシビリティのタイムアウトは、Robolectric でしか確かめておらず、実機ではまだ確かめていません。
  • Flutter は、時間で閉じるスナックバーを Android のアクセシビリティのタイムアウトまでは延ばしません。Flutter はこの値を公開していないためです。
  • React Native は、フォーカスが前にどの要素にあったかを知ることができません。フォーカスが中にあるままスナックバーが閉じると、フォーカスはプラットフォームが決めた場所へ移ります。
  • React Native には、セーフエリアを知るための独自の API がありません。スナックバーがセーフエリアの内側に収まるのは、TanreiProvider にウィンドウのインセットを渡したときだけです。渡さないと、ホームインジケーターのある端末では一部がそれに重なることがあります。
  • React Native は、アクションを別の行に回すかどうかをレイアウト後の行から決めるので、別の行に回るのは最初のレイアウトの後になります。
  • React Native:控えめな読み上げ、キーボードより上に保つこと、バックグラウンドとポインターでの一時停止は、実機ではまだ確かめていません。

利用者側で行うこと

  • メッセージは、読み上げられたときにそれだけで意味が通るプレーンテキストで書きます。
  • 閉じるボタンには closeLabel を、状態の種類には statusLabel を、文章が長くなりうるスナックバーには expandLabel を、どれもプロダクトの言語で付けます。
  • ホストはページの最後に、Compose ではレイアウトの最後に置きます。
  • React Native では、スナックバーがシステムバーにかからないように、TanreiProvider にウィンドウの insets を渡します。
  • アクション付きのスナックバーに short や long を選ぶなら、同じアクションを時間切れのない場所にも用意します。
  • 人が必ず対応しなければならないことは、スナックバーではなくダイアログで伝えます。