Badge
Badges
A dot for "something is new", or a short count. The variant follows the content: no children is the small badge, any children is the large one.
On navigation destinations, where badges are most often used. The badge is announced after its destination.
A drawer badge is a different affordance in Material: a trailing count in the item's own text color, not an anchored pill.
import {
Badge,
BadgeAnchor,
Icon,
NavigationBar,
NavigationDrawer,
Surface,
Tabs,
Text,
} from '@language-lit/material3-expressive'
export function BadgeExample() {
return (
<Surface
as="section"
aria-labelledby="badge-example-title"
color="surface-container-low"
shape="extra-large"
className="badge-example"
>
<Text
as="h2"
id="badge-example-title"
variant="titleLarge"
emphasis="emphasized"
>
Badges
</Text>
<Text as="p" variant="bodyMedium">
A dot for "something is new", or a short count. The variant follows the
content: no children is the small badge, any children is the large one.
</Text>
<div className="badge-example__row">
<BadgeAnchor badge={<Badge label="New activity" />}>
<Icon source="notifications" />
</BadgeAnchor>
<BadgeAnchor badge={<Badge label="3 unread lessons">3</Badge>}>
<Icon source="school" />
</BadgeAnchor>
<BadgeAnchor badge={<Badge label="More than 99 unread">99+</Badge>}>
<Icon source="mail" />
</BadgeAnchor>
<BadgeAnchor badge={<Badge label="999 or more waiting">999+</Badge>}>
<Icon source="inbox" />
</BadgeAnchor>
</div>
<Text as="p" variant="bodyMedium">
On navigation destinations, where badges are most often used. The badge
is announced after its destination.
</Text>
<NavigationBar
className="badge-example__bar"
items={[
{
value: 'lessons',
label: 'Lessons',
icon: <Icon source="school" />,
badge: <Badge label="3 new lessons">3</Badge>,
},
{
value: 'inbox',
label: 'Inbox',
icon: <Icon source="inbox" />,
badge: <Badge label="Unread messages" />,
},
{ value: 'profile', label: 'Profile', icon: <Icon source="person" /> },
]}
/>
<Tabs
className="badge-example__tabs"
items={[
{
value: 'all',
label: 'All',
icon: <Icon source="list" />,
badge: <Badge label="12 items">12</Badge>,
},
{ value: 'saved', label: 'Saved', icon: <Icon source="bookmark" /> },
]}
/>
<Text as="p" variant="bodyMedium">
A drawer badge is a different affordance in Material: a trailing count in
the item's own text color, not an anchored pill.
</Text>
<NavigationDrawer
className="badge-example__drawer"
variant="permanent"
items={[
{
value: 'inbox',
label: 'Inbox',
icon: <Icon source="inbox" />,
badge: '24',
},
{
value: 'spam',
label: 'Spam',
icon: <Icon source="report" />,
badge: '99+',
},
{ value: 'trash', label: 'Trash', icon: <Icon source="delete" /> },
]}
/>
</Surface>
)
}Badgeはアイコンに付ける小さな通知マーカーです。「新着あり」を示す点、または短い件数を表示します。BadgeAnchorは対象コンテンツにバッジを配置します。
import { Badge, BadgeAnchor, NavigationBar, Tabs } from '@language-lit/material3-expressive'
import '@language-lit/material3-expressive/styles.css'
// A dot: no content, so it is the small variant.
<Badge label="New notifications" />
// A count: any content makes it the large variant.
<Badge label="3 unread messages">3</Badge>
// Anchored to an icon.
<BadgeAnchor badge={<Badge label="3 unread">3</Badge>}>
<Icon name="mail" />
</BadgeAnchor>
// On a navigation destination, where badges are most often used.
<NavigationBar
items={[
{ value: 'inbox', label: 'Inbox', icon: <Icon name="inbox" />, badge: <Badge label="3 unread">3</Badge> },
{ value: 'sent', label: 'Sent', icon: <Icon name="send" /> },
]}
/>
// On a tab.
<Tabs
items={[
{ value: 'all', label: 'All', icon: <Icon name="list" />, badge: <Badge label="2 new">2</Badge> },
{ value: 'archive', label: 'Archive' },
]}
/>仕様
variantやsizeのpropsはありません。Materialのソースと同じく、content != nullに相当する子要素の有無でバリアントを選びます。子要素がない場合は6pxの点になり、何らかのコンテンツがあれば16pxのピルになります。0件もコンテンツとして扱うため、<Badge>{0}</Badge>は大きいバリアントのままです。
Materialではバッジのテキストは+を含めて4文字までを想定しています。そのため999+が想定上の最大幅です。それより長いテキストも表示でき、ピルは先頭側を基準に外へ広がりますが、仕様どおりの表示ではなくなります。
BadgeAnchorではbadgeにバッジを、childrenに装飾対象のコンテンツを渡します。バッジは通常のレイアウトフローから外れるため、子要素のサイズだけを計測します。バッジを追加しても周囲のレイアウトは変わりません。バッジは上端の末尾側に配置され、右から左へ記述する言語では自動的に反転します。
両コンポーネントは<span>を描画し、classNameをライブラリのクラスの後に結合します。ほかのネイティブ属性はそのまま渡され、refも転送されます。
アクセシビリティ
支援技術に読み上げさせる内容をlabelに指定します。バッジに表示する3だけでなく、label="3 unread messages"のように意味が伝わる文言を渡します。バッジはその名前を持つ画像として公開され、表示上の文字はアクセシビリティツリーから除かれるため、件数が重複して読み上げられません。
バッジに意味がある場合は必ずlabelを指定してください。指定しない場合、バッジにはロールが設定されません。名前付きバッジのテキスト自体は通常のコンテンツとして読まれますが、点だけのバッジは読み上げられません。ほかの要素が同じ情報を伝える場合に限り、これは適切な既定動作です。
バッジは対象のナビゲーション先の後に読み上げられます。NavigationBar、NavigationRail、Tabsでは追加のマークアップは不要です。これらのコンポーネントはアイコンスロットを支援技術から隠し、バッジはその非表示サブツリーの外側に描画します。
強制カラー表示では、バッジをCanvas上のCanvasTextで描画し、Canvas色の輪郭を付けます。作者指定の背景色が無効化されたとき、アイコン上に文字だけが浮いて見えるのを防ぎます。
ナビゲーションとタブでのバッジ
NavigationBar、NavigationRail、NavigationDrawer、NavigationSuite、Tabsは、各項目のbadgeを受け取ります。これは、Materialのソースでバッジ用の領域を設けているコンポーネントと一致します。
Drawerには知っておくべき例外があります。MaterialではDrawer項目のバッジは別の表現です。アイコンに付けるエラーカラーのピルではなく、行の末尾に項目の文字色で単純な件数を表示します。そのためDrawerには文字列を渡し、ほかのコンポーネントにはBadgeを渡します。
<NavigationDrawer
variant="permanent"
items={[
{ value: 'inbox', label: 'Inbox', icon: <Icon name="inbox" />, badge: '24' },
{ value: 'spam', label: 'Spam', icon: <Icon name="report" />, badge: '99+' },
]}
/>トークンとソースの境界
| トークン | 既定値 |
|---|---|
--m3e-comp-badge-color | sys.color.error |
--m3e-comp-badge-label-color | sys.color.onError |
--m3e-comp-badge-shape | sys.shape.corners.cornerFull |
--m3e-comp-badge-size | 6px |
--m3e-comp-badge-large-size | 16px |
--m3e-comp-badge-large-horizontal-padding | 4px |
--m3e-comp-badge-offset | 6px |
--m3e-comp-badge-large-horizontal-offset | 12px |
--m3e-comp-badge-large-vertical-offset | 14px |
Materialのソースでは、コンテナー色とコンテンツ色を呼び出しごとのパラメーターとして受け取ります。このライブラリでは代わりにトークンとして公開します。インスタンスごとに任意の値を指定するにはインラインスタイルで出力する必要がありますが、スコープ付きカスタムプロパティの上書きならテーマと合成でき、Material3Providerの入れ子やサーバーレンダリングにも対応します。
ソースではどちらのバリアントもCornerFullを使うため、両方で1つのshapeトークンを共有します。デザイン仕様にある「3dp」と「8dp」の別々の角丸は、6pxの点と16pxのピルで同じ形状を示しています。
サイズはソースのdefaultMinSizeに合わせて最小値として設定します。バッジをその最小値より小さくするには、サイズだけでなく最小サイズも上書きしてください。サイズだけを変更しても下限が残ります。
ラベルのタイポグラフィはバッジ用トークンに登録せず、ほかのすべてのコンポーネントと同様にタイプスケールのlabel-smallを直接参照します。