Skip to content
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
Getting startedWebReactSwiftUIJetpack ComposeFlutterReact NativePresetsYour own design systemUsing Tanrei with AI agentsBrandDesign tokensDesign systems and customizationTheme StudioColorTypographyIconsShapeElevationMotionInteraction statesBehaviourLayoutAccessibilityWordsColor tokensTypescale tokensShape tokensElevation tokensMotion tokensState tokensSpace and measure tokensComponentsButtonMain action buttonSegmented buttonButton groupButton with menuToolbarCopy buttonCheckboxRadio buttonSwitchChipMenuSliderSlider fieldCalendarDateTimePickerField groupRadio groupCheckbox groupSelectSelect fieldMenubarFile uploadFile picker AdvancedDrop zoneText input AdvancedText fieldChip fieldFieldCombobox AdvancedCombobox fieldNumber input AdvancedNumber fieldColor input AdvancedColor fieldOne-time-code input AdvancedOne-time-code fieldEmbedded text input AdvancedChat composerCardDividerModal layer AdvancedDialogListList rowBottom sheetSide sheetCarouselCode blockTablePopoverStepsAccordionDescription listFigureApp shellPage headerTree viewTimelineImage previewHandleResizable frameScroll areaBadgeProgress indicatorMeterNumber tickerLoading indicatorSnackbarTooltipKeyboard shortcutTagInline codeCalloutAvatarEmpty stateTag groupSkeletonFile itemSwatchMarquee textTabsTab viewApp barNavigation barNavigation drawerSearchLinkPaginationPrevious and next linksStepperBreadcrumbsTable of contentsLink cardFAB menuStatusHow Tanrei is builtContributingLicense and attribution
HomeComponentsCallout

Callout

A notice that is part of the content and stays there — a note, a caution, the reason for something or a summary of what is wrong — as a quiet fill or an outline, in a status's colors with its glyph, with an optional title, actions and a close button.

content_copyView as Markdown
Category
Communication
Tokens
comp.callout
Level
Base

Overview

Web
tanrei-callout
schedule
SwiftUI
TanreiCallout
radio_button_unchecked
Jetpack Compose
TanreiCallout
radio_button_unchecked
Flutter
TanreiCallout
radio_button_unchecked
React Native
Callout
radio_button_unchecked

Generated from docs/status/status.json, specs/callout.spec.json

A note, a caution with a title, and a notice with actions and a close button

The web draws this revision; the other platforms follow.The migration rewrites the database in place. The steps below show how to keep a copy you can restore.Reload to read it. Nothing you typed is lost.ReloadShow the last one again

A callout is a notice in the flow of the page: it stands where you put it, as wide as its place, for as long as the content does. It is a note by default, read where it stands like the paragraph before it, and it never takes focus.

On the web it is <tanrei-callout>. The other platforms have not implemented it yet.

Usage

The body is the element’s own content. The title is the headline attribute, or the title slot where it is your own content or a heading. A glyph of the icon set goes in the icon slot, and your own Buttons or links in the actions slot.

<!-- A note. Its status is in its title's words. -->
<tanrei-callout status="info">
  <p slot="title">Note</p>
  Text styles are named by level, never by size.
</tanrei-callout>

<!-- The title does not say the status: status-label is read first ("Warning, …") and not drawn. -->
<tanrei-callout status="warning" status-label="Warning" headline="Accessibility">
  A preset is held to its original, not to these contrast thresholds.
</tanrei-callout>

<!-- A tip: no status, your glyph, the word in the title. -->
<tanrei-callout headline="Tip">
  <span slot="icon">lightbulb</span>
  Keep an action enabled and explain what is missing when it is used.
</tanrei-callout>

<!-- Long-form content is prose, at the callout's size, with no measure. -->
<tanrei-callout size="large" status="warning">
  <h3 slot="title">Back up before you migrate</h3>
  <div class="tanrei-prose" data-size="large" data-measure="false">
    <p>The migration rewrites the database in place. <a href="#restore">See how to restore a copy</a>.</p>
    <ul><li>…</li></ul>
  </div>
</tanrei-callout>

<script type="module">
  import '@tanrei/web/components/callout';
  import '@tanrei/web/callout.css'; // the box a server-rendered callout keeps before the element is defined
</script>
import { Button, Callout } from '@tanrei/react';

<Callout status="warning" headline="The file changed on disk" live open={conflict} closable closeLabel="Close" onDismiss={() => setConflict(false)}>
  You have edits here that are not in it. Nothing is overwritten until you choose.
  <Button slot="actions" variant="outlined" onClick={reload}>Reload from disk</Button>
  <Button slot="actions" variant="text" onClick={keep}>Keep mine</Button>
</Callout>

Specs

Both looks in every status

Sizes are minimums: a control grows with enlarged text.The web draws this revision; the other platforms follow.A border that is the only cue a control exists must keep 3:1 against its surface.The file could not be read. Nothing was changed.Every contrast check passes in all six schemes.Sizes are minimums: a control grows with enlarged text.The web draws this revision; the other platforms follow.A border that is the only cue a control exists must keep 3:1 against its surface.The file could not be read. Nothing was changed.Every contrast check passes in all six schemes.

A title, your own glyph, actions, a close button, and the large size

lightbulbKeep an action enabled and explain what is missing when it is used.scienceThis behaviour may change in a later version.You have edits here that are not in it. Nothing is overwritten until you choose.Reload from diskKeep minePress the close button: the callout goes at once, and focus returns to where it was.Show it againThis command deletes every generated file and cannot be undone.

Anatomy

Anatomy

Drawn over the fixture tonal, as the web package draws it.
Part Description
1 container The block that holds the notice, as wide as its place in the layout and as tall as its content, flat on the surface it sits on. Filled in the tonal look (a quiet neutral, or a status's quiet container) and edged in the outlined look. It carries no state: nothing about it answers a pointer, a press or focus.
2 outline · optional The outlined look's edge: one hairline inside the container, the same in every status.
3 icon · optional The glyph at the start, on the first line of the text: the status's glyph from the system's icon set (info, warning, error or check_circle, the ones a snackbar draws), or the glyph the product names. In the status's own color, a little softer than the text. Decorative: the status is given in words.
4 title · optional A few words that name the notice, above the body. Plain text unless the product gives a heading; it wraps and is never cut.
5 body The notice itself: the product's content, from one sentence to several paragraphs with lists, links, code and tables. Long-form blocks in it are prose.
6 actions · optional One or two of the product's own Buttons or links that follow up on the notice, in a row below the text.
7 close-button · optional An icon-only Button of the small size showing the close glyph at the end of the first row, in the variant comp.callout.close-button.variant names (tonal in the default system) and, on a filled look, in the tone of that fill: its content color, so that its container is that color made translucent and its glyph is that color. The callout places it and chooses its variant and its tone; everything else it reads from Button's own tokens. The only part of a callout that answers a pointer, a press or focus, and it answers as a Button.

Measurements

Measurements

Length Token Value (px)
container padding-inline tanrei.comp.callout.container.padding-inline 16
Drawn over the fixture tonal, as the web package draws it.

Properties

Property Type Default Description
title string "" A few words above the body that name the notice ("Not published yet"). Plain text, never a heading. Empty draws the body alone. The title slot takes its place where the product needs a heading of its own level or inline content.
status default | info | warning | error | success "default" What the notice is, in the names the snackbar and the tag use. default: a neutral aside, with no glyph unless the product gives one. info, warning, error and success draw the look in that status's color roles with that status's glyph; a live callout is announced assertively for warning and error. Use them for what they mean everywhere in the system: advice is not a success.
statusLabel string "" The status in words, in the product's language ("Warning"), read by assistive technology before the title and the body and not drawn, so that the status never rests on color and glyph alone. For a callout whose title does not itself say the status; needed when it has a status and no title. Ignored while status is default.
variant tonal | outlined "tonal" How much the callout weighs. tonal: a quiet fill, the default. outlined: no fill and one hairline, for a callout on a tinted surface or inside a filled card, where a fill would vanish, and for many callouts close together. A design system may add values.
size medium | large "medium" The size of the text, in prose's two sizes. medium sets the body in the interface's own text style, for a callout among controls: a form, a panel, a dialog. large sets it in the style for long reading, for a callout in a page whose purpose is the text. Paddings, the glyph and the close button are the same at both. A design system may add values.
live boolean false Whether the callout is announced. False, the default, is for a callout that is part of what was written: it is a note, read where it stands. True is for one that appears or changes in answer to something (a summary of a form's errors after a submit, a notice that the file changed underneath the person): it is a live region, announced once when it is shown and when its content changes, politely or, for warning and error, assertively. It changes nothing that is drawn.
closable boolean false Draws a close button, for a notice the person may put away (an announcement, a hint they have read). A caution that qualifies the content beside it should not be closable.
closeLabel string "" The close button's accessible name, in the product's language ("Close"). Required while closable is true.
open boolean true Whether the callout is shown. The close button sets it to false; the product sets it to show a callout again or to show one in answer to something. A callout that is not open draws nothing and takes no space.

Slots

Slot Description
default The body: text, or the product's own blocks (paragraphs, lists, a table, a code block, any component). Long-form blocks are laid out as prose of the callout's size.
icon A glyph of the system's icon set, by name, drawn in place of the status glyph, or as the glyph of a callout without a status (a tip's lightbulb).
title The product's own title content in place of the title prop: text with inline content, or a heading of the level the page's outline needs. The callout draws it in the title's style and changes neither its role nor its level.
actions The actions: the product's own Buttons or links, one or two. The callout lays them out below the text and does nothing else to them.

Events

Event Description
dismiss The person closed the callout with its close button. Emitted exactly once, after open became false; never emitted when the product sets open.

States

Behavior

Requirements in EARS form. Every platform implementation is tested against these ids.

ID Requirement
CLO-1 THE callout SHALL draw a container as wide as the layout it is placed in gives it (CLO-22) and as tall as its content, with corners comp.callout.container.shape eased by sys.shape.corner-smoothing and no elevation, padded by comp.callout.container.padding-block above its first line and below its last block, by comp.callout.container.padding-inline at the start, and at the end by comp.callout.container.padding-inline where the text meets it or by comp.callout.container.padding-inline-end-with-close after the close button. Inside it SHALL lay out, from the start: the glyph, where it has one (CLO-5, CLO-6); comp.callout.icon.gap after it a column that takes the remaining width and holds the title (CLO-7), the body (CLO-8) and the actions (CLO-9), in that order; and comp.callout.container.gap after that column the close button, where it has one (CLO-10). THE callout SHALL centre the glyph and the close button on the first line of the text (the title's, or the body's without a title) and keep them there however tall the text grows, and any height the container has beyond its content SHALL lie below the content.
CLO-2 WHILE status is default THE callout SHALL fill its container with the container color of its variant and draw its title, its body and a glyph the product gave in that variant's title, body and icon colors: comp.callout.container.color.tonal, comp.callout.title.color.tonal, comp.callout.body.color.tonal and comp.callout.icon.color.tonal for tonal, and outlined's tokens of the same names (comp.callout.container.color.outlined, comp.callout.body.color.outlined, and so on).
CLO-3 WHILE status is info, warning, error or success THE callout SHALL draw its container, its title, its body and its glyph from the parts of its variant prefixed with the status: for a tonal info callout comp.callout.info.container.color.tonal, comp.callout.info.title.color.tonal, comp.callout.info.body.color.tonal and comp.callout.info.icon.color.tonal; for outlined, and for warning, error and success, their own tokens of the same names (comp.callout.error.icon.color.outlined, comp.callout.success.container.color.tonal, and so on). A status SHALL change these colors, the glyph (CLO-5), the tone of the close button (CLO-10) and how a live callout is announced (CLO-21), and nothing else: the callout's size, shape, layout and edge stay those of its variant and size.
CLO-4 WHILE variant is outlined THE callout SHALL draw an edge comp.callout.outline.width wide in comp.callout.outline.color inside its container's size, following its corners, in every status, without changing the callout's size or the place of its content; WHILE variant is tonal it SHALL draw no edge. A status SHALL NOT color the edge: it is the system's one hairline.
CLO-5 WHILE status is info, warning, error or success and the icon slot is empty THE callout SHALL draw that status's glyph from the system's icon set (info for info, warning for warning, error for error, check_circle for success), the glyphs a snackbar draws, in a box of comp.callout.icon.size, in its form's icon color (CLO-3) drawn at comp.callout.icon.opacity (an opacity of the glyph alone, which fades nothing else), with sys.icon.family.unselected and sys.icon.fill.unselected, sys.icon.weight and an optical size clamped between sys.icon.optical-size.min and sys.icon.optical-size.max. The glyph so drawn SHALL keep 3:1 against its container in every scheme of the default system, and against every surface role in the outlined look. THE callout SHALL keep the glyph from assistive technology, which is given the status in words (CLO-20). WHILE status is default and the icon slot is empty THE callout SHALL draw no glyph and no room for one: the text starts at the start padding.
CLO-6 WHERE the icon slot holds a glyph THE callout SHALL draw it in the place, the box, the color and the opacity CLO-5 gives the status glyph (the icon color of its form, CLO-2 and CLO-3), in every status and without one, SHALL draw no status glyph beside it, and SHALL keep it from assistive technology: the product's glyph says what kind of notice this is (advice, an experiment, something new), and the words say it to everyone.
CLO-7 WHILE title is not empty THE callout SHALL draw it above the body in the text style of its size (comp.callout.title.typescale.medium for medium, comp.callout.title.typescale.large for large) and the title color of its form (CLO-2, CLO-3), with comp.callout.title.gap between it and the body; the title SHALL wrap and SHALL never be cut. WHERE the title slot holds content THE callout SHALL draw that content in the title's place, style and color instead of the title prop, and SHALL change neither its role nor its level: a title given as text is plain text and never a heading, and a title the product gives as a heading stays a heading of the level the product gave it, drawn as every other title is. WHILE both are empty THE callout SHALL draw the body alone, on the first line. THE callout 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.
CLO-8 THE callout SHALL draw the content of its default slot as its body, below the title, and SHALL give it the text style of its size (comp.callout.body.typescale.medium for medium, comp.callout.body.typescale.large for large) and the body color of its form (CLO-2, CLO-3) as the style and color its text takes where nothing inside sets another. The body SHALL wrap and SHALL never be cut or limited to a number of lines. WHERE the body holds long-form blocks (paragraphs, lists, a quote, a table, a code block) they SHALL be laid out as prose of the callout's size (PRS-1, PRS-5), without a measure of their own, so that the first block has no space before it and the last none after it and the body is exactly as tall as its content; the callout SHALL add no style to anything inside a component placed in its body, and a link in the body SHALL be underlined at rest (PRS-17).
CLO-9 WHERE the actions slot holds content THE callout SHALL lay it out as a row below the text (the body, or the title where there is no body), comp.callout.actions.gap below it, starting at the text's start edge, with comp.callout.actions.item-gap between two actions along the row and between rows where they wrap; an action's label SHALL NOT be cut to make the row fit. The actions are the product's own Buttons and links: THE callout SHALL NOT restyle them, SHALL NOT answer their presses and SHALL NOT close when one is activated. They SHALL follow the body in reading order and in the focus order, before the close button.
CLO-10 WHILE closable is true THE callout SHALL draw at the end of its first row an icon-only Button of the small size showing the glyph close of the system's icon set and named by closeLabel, in the variant comp.callout.close-button.variant names and, in every look but outlined, in the tone of its form (BTN-45): the body color of that form (comp.callout.body.color.tonal without a status, comp.callout.info.body.color.tonal for info, and so on), the content color of the fill it stands on, so that its container is that color made translucent and its glyph is that color; both given through the channel a Button reads from the component that places it, and otherwise unrestyled: it reads Button's own tokens, answers hover, focus and press as any Button does and keeps its own minimum target (BTN-5). The outlined look has no fill, so its close button takes no tone and is drawn as the variant draws it on the surface the callout sits on. THE callout's container, its text and its glyph SHALL NOT change under a pointer, a press or focus: the callout itself has no states. WHILE closable is false THE callout SHALL draw no close button and keep no room for one.
CLO-11 WHEN the user activates the close button by pointer, touch, Enter or Space THE callout SHALL set open to false, emit dismiss exactly once and close (CLO-13). THE callout SHALL NOT close by itself after a time, and SHALL NOT answer Escape: it is part of the content, and Escape belongs to whatever overlay or field the person is in.
CLO-12 WHEN a callout closes while focus is inside it THE callout SHALL move focus to the element that had it before focus entered the callout or, where that element no longer exists or cannot take focus, leave focus to the platform's default, so that the next Tab reaches what followed the callout; WHEN it closes while focus is elsewhere it SHALL NOT move focus.
CLO-13 WHILE open is false THE callout SHALL draw nothing, take no space and expose nothing to assistive technology, and its content SHALL NOT be reachable by focus; WHEN open changes THE callout SHALL appear or disappear at once, without a motion of its own, and SHALL emit dismiss only for a close the person made (CLO-11), never when the product sets open.
CLO-19 THE callout SHALL ease its container 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); a platform that cannot draw it SHALL fall back to a quarter circle and state that in its README. Its close button's corners and its focus indicator are the Button's (BTN-10).
CLO-20 WHILE live is false THE callout SHALL be exposed to assistive technology as a note: a part of the content that qualifies what is around it, read where it stands in reading order, named by its title where it has one and otherwise by statusLabel where status is not default and the product gave one. WHILE status is not default and statusLabel is not empty THE callout SHALL expose statusLabel as text before the title and the body, set apart so that it is read as a phrase of its own ("Warning, …"), and SHALL NOT draw it; WHILE status is default it SHALL ignore statusLabel. A callout that is not live SHALL NOT be announced when it is shown or when its content changes, SHALL NOT move focus, and SHALL NOT be a stop in the focus order: static content is not an alert.
CLO-21 WHILE live is true THE callout SHALL be a live region in place of a note. WHEN it is shown after its page or view was first presented (open becomes true, or the product adds it), and WHEN its title, its body or its status changes while it is shown, THE callout SHALL have assistive technology announce it once: statusLabel first when status is not default, then the title when it has one, then the whole body. 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, as a snackbar does, and SHALL NOT move keyboard or screen-reader focus. A live callout that is drawn with the first presentation of its page or view SHALL NOT announce itself then. live SHALL change nothing that is drawn.
CLO-22 THE callout SHALL lay itself out as a block in the flow of its container: it SHALL take the whole inline size the container gives it, in a page, in a list item, in a tab's panel, in a card or in another callout's body alike, with its start edge on the start edge of the text around it; it SHALL bring no outer space of its own (PRS-15: the space between it and its neighbours is its container's), SHALL never be wider than its container, SHALL have no minimum width, no maximum width and no measure of its own, and SHALL NOT scroll: content that cannot shrink (a table, a line of code) scrolls inside its own box. THE callout SHALL NOT float over the content, stick to an edge while the content scrolls or raise itself above it: where it stands is the product's layout.
CLO-23 WHILE the text size is enlarged THE callout SHALL grow its title and its body with it, keep its paddings and gaps, wrap, and grow in height; it SHALL keep the glyph's box at comp.callout.icon.size and the close button at its own size (a Button's icon-only circle, BTN-12), both still centred on the first line (CLO-1), SHALL cut nothing and set no fixed height, SHALL lose no content when the reader raises line height, paragraph spacing, letter spacing or word spacing, and SHALL need no horizontal scrolling at a width of 320 points with text at 200 %.
CLO-24 WHERE the platform draws with a person's own forced colors (the web's forced-colors mode, and a platform's equivalent where it has one) THE callout SHALL draw an edge comp.callout.outline.width wide in the platform's text color inside its container in every variant and status, its title, body and glyph in that text color with the glyph whole (no opacity), links in the platform's link color and still underlined, and its close button as a Button draws itself there, so that a look whose fill the mode removes is still a bounded block; the status SHALL then rest on the glyph's shape, the title's words and statusLabel.
CLO-25 WHERE the layout direction is right-to-left THE callout SHALL mirror the order of its content and apply its paddings by logical start and end: the glyph at the right, the text after it, the actions starting at the text's right edge and the close button at the left.
CLO-26 THE callout 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 callout is shown THE callout SHALL draw itself for the new direction at once, without a motion of its own, keeping its open state and its focus.
CLO-27 WHERE the layout direction is right-to-left THE callout SHALL draw the glyph of its icon slot mirrored left to right exactly when the system's icon set marks that glyph's name as mirrored or the product forces mirroring for that use, and unmirrored when the product forbids it: the icon helper mirrors the glyph inside its own box, about the box's vertical centre line, changing neither its place, size, color and opacity nor the order CLO-25 gives. THE callout SHALL NOT mirror a glyph itself, SHALL NOT mirror content the product gives as an image or a view of its own rather than by a glyph's name, and its own glyphs (the four status glyphs) are drawn through the same helper, which mirrors none of them under the default icon set; the close button's glyph is the Button's (BTN-19).
CLO-28 THE callout SHALL lay out a title given as text as text of its own direction, isolated from the text around it: it takes the base direction of its first strong character, or the layout direction where it has none, so that a left-to-right name, a number with its unit and a version keep their own order in a right-to-left layout and reorder nothing beside them; THE callout SHALL still place the title and align its lines by the layout direction, not by the text's own. The body's blocks are laid out as prose lays out a block (PRS-25): each by its own first strong character unless the content states a direction, with its lines aligned to the start edge of the layout.
CLO-29 WHILE the system's icon set cannot yet draw a glyph the callout draws itself (a status's glyph, or the icon slot's; its font is still loading) THE callout 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 and closeLabel SHALL still reach assistive technology. The close button's glyph is the Button's to draw.
CLO-30 WHEN variant or size names a value that the active design system does not have, or status names a value this spec does not have, THE callout SHALL render with the default value (the default variant's or size's tokens, status default), and in debug builds report the unknown value.
CLO-31 IF the title, the title slot and the body are all empty THE callout SHALL draw nothing, take no space and expose nothing, and in debug builds report that a callout has no content: a glyph, actions or a close button alone are not a notice. WHILE status is not default and both the title and statusLabel are empty THE callout SHALL still render and in debug builds report that the status is not given in words; 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; IF statusLabel is not empty while status is default THE callout SHALL ignore it and in debug builds report it.
CLO-32 WHERE the platform has ARIA attributes (the web), the host carries its note or live region role (CLO-20, CLO-21) itself, so what the product writes on it is not moved, but for a name: WHILE its name (its title, else statusLabel) is not empty THE callout SHALL hold the product's aria-label and aria-labelledby on that host as their data-aria- twins, so that its name (its title, else statusLabel) is its whole name although the platform gives aria-labelledby the precedence, and WHEN its name (its title, else statusLabel) is emptied SHALL give them back as they were written; WHILE its name (its title, else statusLabel) is empty a name the product gives so — aria-label, or aria-labelledby naming an element — SHALL be its name. WHILE it is closed or empty (CLO-13) it exposes nothing, and SHALL hold them as well, giving them back when it is shown. WHEN the product removes one, or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaLabelledByElements) to null, THE callout SHALL take it away.

Platform name mapping

Parity does not mean identical APIs. Each platform uses its own idiom for the same contract.

Spec Web SwiftUI Jetpack Compose Flutter React Native
Component tanrei-callout TanreiCallout TanreiCallout TanreiCallout Callout
title · Property headline title title title title
status · Property status status status status status
statusLabel · Property statusLabel statusLabel statusDescription statusSemanticLabel statusAccessibilityLabel
variant · Property variant variant variant variant variant
size · Property size size size size size
live · Property live isLive live live live
closable · Property closable isClosable closable closable closable
closeLabel · Property closeLabel closeLabel closeDescription closeSemanticLabel closeAccessibilityLabel
open · Property open isPresented visible visible open
dismiss · Event dismiss onDismiss onDismiss onDismiss onDismiss
Notes <tanrei-callout> with the body as light-DOM children, the glyph in the icon slot, the title in the title slot (<p slot="title">, or <h3 slot="title"> where it is a heading) or the headline attribute (HTML's global title attribute would show a tooltip over the whole block, as the snackbar's and the app bar's), and Buttons or links in the actions slot. statusLabel and closeLabel are the status-label and close-label attributes. open is a boolean property that defaults to true; the open attribute spells it, and open="false" is how markup starts a callout closed. The host is display: block with no margin, role="note" and aria-labelledby naming the title, set through ElementInternals where axe can see it and as attributes otherwise; with live the host's role is status or alert by status, and the announcement is made through a visually hidden live region in the shadow root that exists from the element's first render, so that a callout opened later is announced (CLO-21). statusLabel is drawn as visually hidden text before the title in the shadow root, followed by a comma and a space; the glyph is aria-hidden. The body takes the form's content color and the size's text style through inherited color and font, so text placed directly in the element is styled, and a body that is rendered Markdown is wrapped by the product in <div class="tanrei-prose" data-measure="false"> with the callout's data-size (prose re-enters inside a Tanrei element). The glyph's opacity is applied to its color (a mix with transparent), never as an element's opacity. The close button is a <tanrei-button size="small"> with the close glyph in its icon slot and close-label as its label, in the shadow root (part close-button); the callout gives it the variant --tanrei-comp-callout-close-button-variant names and, in a look with a fill, the tone of its form (the form's body-color custom property) through the Button's internal channel, after each update (CLO-10). Under forced-colors: active the container takes a CanvasText border of the outline width (CLO-24). The stylesheet of @tanrei/web gives tanrei-callout:not(:defined) display: block, so the content keeps its place before the element is defined. dismiss is a CustomEvent. TanreiCallout(_ title: String? = nil, status:, statusLabel:, variant:, size:, isLive:, isClosable:, closeLabel:, isPresented:, icon:, onDismiss:) { body } actions: { … }, the body and the actions as @ViewBuilders, the glyph by name through icon:, and a second initializer whose title is a @ViewBuilder (the title slot; .accessibilityAddTraits(.isHeader) is the product's). isPresented is a Binding<Bool> that defaults to a constant true. The container is an accessibility container labelled by the title or statusLabel; a live callout posts AccessibilityNotification.Announcement at default priority, or high for warning and error. TanreiCallout(title, status, statusDescription, variant, size, live, closable, closeDescription, visible, icon, onDismiss, titleContent, actions) { body }, the body, titleContent and actions as composable lambdas, the glyph by name through icon. visible is hoisted state: the close button calls onDismiss and the product sets it. The container merges no descendants and carries paneTitle-free semantics: a content description of the status description on a leading invisible node, and for a live callout liveRegion polite or assertive by status. TanreiCallout(title: …, status:, statusSemanticLabel:, variant:, size:, live:, closable:, closeSemanticLabel:, visible:, icon:, titleWidget:, actions:, onDismiss:, child:), the body as child, the title slot as titleWidget, the actions as a list of widgets, the glyph by name through icon. visible is the product's state; the close button calls onDismiss. A Semantics(container: true, label:) over the block, with liveRegion: true for a live callout and SemanticsService.announce with its assertiveness for warning and error. <Callout title="…" status="warning">…</Callout>; the body is children, the title slot is the titleContent prop, the actions are the actions prop (elements), and the glyph is the icon prop (a glyph name). open is controlled when given, with onDismiss. A live callout sets accessibilityLiveRegion (polite or assertive) on Android and calls AccessibilityInfo.announceForAccessibility on iOS. Follows the app's direction where an ancestor's is not known.

Tokens

Token Aliases Resolved (light)
--tanrei-comp-callout-actions-gap sys.space.space150 12px
--tanrei-comp-callout-actions-item-gap sys.space.space100 8px
--tanrei-comp-callout-body-color-outlined sys.color.on-surface #1b1b1b
--tanrei-comp-callout-body-color-tonal sys.color.on-secondary-container #262626
--tanrei-comp-callout-close-button-variant — 1
--tanrei-comp-callout-container-color-outlined — #000000 · 0%
--tanrei-comp-callout-container-color-tonal sys.color.secondary-container #eeeeee
--tanrei-comp-callout-container-gap sys.space.space100 8px
--tanrei-comp-callout-container-padding-block sys.space.space150 12px
--tanrei-comp-callout-container-padding-inline sys.space.space200 16px
--tanrei-comp-callout-container-padding-inline-end-with-close sys.space.space100 8px
--tanrei-comp-callout-container-shape sys.shape.corner.medium 8px
--tanrei-comp-callout-error-body-color-outlined comp.callout.body.color.outlined #1b1b1b
--tanrei-comp-callout-error-body-color-tonal sys.color.on-error-container #3a211f
--tanrei-comp-callout-error-container-color-outlined comp.callout.container.color.outlined #000000 · 0%
--tanrei-comp-callout-error-container-color-tonal sys.color.error-container #ffedeb
--tanrei-comp-callout-error-icon-color-outlined sys.color.error #cd262f
--tanrei-comp-callout-error-icon-color-tonal sys.color.error #cd262f
--tanrei-comp-callout-error-title-color-outlined comp.callout.error.body.color.outlined #1b1b1b
--tanrei-comp-callout-error-title-color-tonal comp.callout.error.body.color.tonal #3a211f
--tanrei-comp-callout-icon-color-outlined sys.color.on-surface-variant #4b4b4b
--tanrei-comp-callout-icon-color-tonal comp.callout.body.color.tonal #262626
--tanrei-comp-callout-icon-gap sys.space.space150 12px
--tanrei-comp-callout-icon-opacity — 0.85
--tanrei-comp-callout-icon-size sys.icon.small 20px
--tanrei-comp-callout-info-body-color-outlined comp.callout.body.color.outlined #1b1b1b
--tanrei-comp-callout-info-body-color-tonal sys.color.on-info-container #212839
--tanrei-comp-callout-info-container-color-outlined comp.callout.container.color.outlined #000000 · 0%
--tanrei-comp-callout-info-container-color-tonal sys.color.info-container #edf0ff
--tanrei-comp-callout-info-icon-color-outlined sys.color.info #3066cb
--tanrei-comp-callout-info-icon-color-tonal sys.color.info #3066cb
--tanrei-comp-callout-info-title-color-outlined comp.callout.info.body.color.outlined #1b1b1b
--tanrei-comp-callout-info-title-color-tonal comp.callout.info.body.color.tonal #212839
--tanrei-comp-callout-outline-color sys.color.outline-variant #000000 · 8%
--tanrei-comp-callout-outline-width sys.border.hairline 1px
--tanrei-comp-callout-success-body-color-outlined comp.callout.body.color.outlined #1b1b1b
--tanrei-comp-callout-success-body-color-tonal sys.color.on-success-container #1b2c1c
--tanrei-comp-callout-success-container-color-outlined comp.callout.container.color.outlined #000000 · 0%
--tanrei-comp-callout-success-container-color-tonal sys.color.success-container #e2f6df
--tanrei-comp-callout-success-icon-color-outlined sys.color.success #077b32
--tanrei-comp-callout-success-icon-color-tonal sys.color.success #077b32
--tanrei-comp-callout-success-title-color-outlined comp.callout.success.body.color.outlined #1b1b1b
--tanrei-comp-callout-success-title-color-tonal comp.callout.success.body.color.tonal #1b2c1c
--tanrei-comp-callout-title-color-outlined comp.callout.body.color.outlined #1b1b1b
--tanrei-comp-callout-title-color-tonal comp.callout.body.color.tonal #262626
--tanrei-comp-callout-title-gap sys.space.space50 4px
--tanrei-comp-callout-warning-body-color-outlined comp.callout.body.color.outlined #1b1b1b
--tanrei-comp-callout-warning-body-color-tonal sys.color.on-warning-container #362410
--tanrei-comp-callout-warning-container-color-outlined comp.callout.container.color.outlined #000000 · 0%
--tanrei-comp-callout-warning-container-color-tonal sys.color.warning-container #ffeee0
--tanrei-comp-callout-warning-icon-color-outlined sys.color.warning #9b5c00
--tanrei-comp-callout-warning-icon-color-tonal sys.color.warning #9b5c00
--tanrei-comp-callout-warning-title-color-outlined comp.callout.warning.body.color.outlined #1b1b1b
--tanrei-comp-callout-warning-title-color-tonal comp.callout.warning.body.color.tonal #362410
--tanrei-comp-callout-body-typescale-large sys.typescale.body-large 400 16px/1.625
--tanrei-comp-callout-body-typescale-medium sys.typescale.body 400 14px/1.5714
--tanrei-comp-callout-title-typescale-large sys.typescale.heading-5 500 16px/1.5
--tanrei-comp-callout-title-typescale-medium sys.typescale.heading-6 500 14px/1.4286

Fixtures

Canonical renderings. Every platform snapshots each fixture under the same id so the results can be compared.

Generated from specs/callout.spec.json, tokens/comp/callout.tokens.json, design/tokens/

Guidelines

Snackbar, callout, dialog

The three are told apart once, by where they stand and how long they stay:

Kind What it is Where it stands How long it stays Announced
Snackbar The result of something the app just did or that just happened. Above the content, at the window’s edge. Briefly, or until it is closed. Always.
Callout A notice that is part of the content. In the flow, where you put it. As long as the content does. Only when it appears in answer to something (live).
Dialog A question the person must answer before going on. Above everything, with a scrim. Until it is answered. By taking focus.

A banner — a notice for a whole page or screen, above its content — is not a fourth thing. It is a callout that you place above that region’s content, usually live, usually with an action or a close button. Put it where it comes first in reading order for the region it concerns.

A message under one field (this value is not a date) is not a callout either: it is the field’s own supporting text.

Choosing a look

variant Draws Use for
tonal (default) A quiet fill. Most callouts.
outlined No fill and one hairline. A callout on a tinted surface or inside a filled card, where a fill would vanish, and many callouts close together.

The hairline is the system’s one hairline in every status. A status never colors it.

Statuses

status is default, info, warning, error or success: the names the snackbar and the tag use, for what they mean everywhere in the system. A status draws its colors and its glyph (info, warning, error, check_circle), and decides how a live callout is announced. It changes nothing else.

What you are writing Use
A note: something worth knowing status="info"
A caution: something that needs attention status="warning"
A danger: something that will go wrong status="error"
A tip: advice the reader may take status="default", the glyph lightbulb in the icon slot, and the title “Tip”

A tip is not a success: nothing succeeded. The icon slot takes any glyph of the icon set, with or without a status, so “experimental” and “new in this version” need no new value either. The word goes in the title.

Sizes

size Title Body Use for
medium (default) 14 on a line height of 20 14 on 22 A callout among controls: a form, a panel, a dialog.
large 16 on 24 16 on 26 A callout in a page whose purpose is the text.

These are prose’s two sizes. Only the text styles change: the paddings, the glyph and the close button are the same.

Title

headline is plain text and never a heading. Most titles are labels of a note, and a page whose outline lists “Note” five times is worse for it. Where a callout opens a part of the page that people navigate to, put a heading of the level your outline needs in the title slot. The callout draws it as any title and changes neither its role nor its level.

On a page that is rendered on a server, give the title in the title slot: a headline attribute is drawn by the element, so it is not shown before the element is defined.

Body

Text placed directly in the element takes the callout’s text style and color. Long-form blocks (paragraphs, lists, a table, a code block) go in a prose of the callout’s size without a measure:

<tanrei-callout status="info">
  <div class="tanrei-prose" data-measure="false">…rendered Markdown…</div>
</tanrei-callout>

Prose brings no color, so it reads on every fill, and its links are underlined. A component placed in the body keeps its own styles. A callout in a prose that keeps a measure is kept to the measure like a text block; everywhere else it fills the width it is given.

Actions and closing

The actions are your own Buttons or links, one or two. Use outlined or text Buttons: a tonal Button has the fill of the tonal callout without a status and cannot be seen as a button there. The callout lays them out in a row that wraps, and does nothing else: it does not close when one is pressed.

closable draws a close button: a small icon-only Button, named by close-label, in the variant --tanrei-comp-callout-close-button-variant names (tonal in the default system). On a tonal callout it is drawn in the callout’s own text color — a translucent circle of that color with the glyph in it — so that it shows on the fill of every status; on an outlined callout, which has no fill, it is the Button as it is anywhere else. It answers hover, focus and a press as every Button does. Pressing it sets open to false and fires dismiss. The callout never closes by itself and does not answer Escape. When a person closes one, remember it: a closable callout that returns on every visit is noise. A caution that qualifies the content beside it should not be closable.

Appearing in answer to something

live is for a callout that appears or changes while the person works: a summary of a form’s errors after a submit, a notice that the file changed underneath them. Keep it in the page with open="false" and set open when it applies.

A live callout, shown in answer to a press

Submit the formEnter a name, choose a start date, and give an address that can receive mail.
<tanrei-callout id="errors" live open="false" status="error" headline="Three fields need attention">
  Enter a name, choose a start date, and give an address that can receive mail.
</tanrei-callout>

<script type="module">
  form.addEventListener('submit', (event) => {
    if (!form.checkValidity()) {
      event.preventDefault();
      document.querySelector('#errors').open = true; // announced once, assertively
    }
  });
</script>

A live callout is announced once when it is shown and when its title, its body or its status changes: politely for default, info and success, and assertively for warning and error. One that is in the page’s markup when the page loads is not announced then.

On the web, a live callout that your script adds to the page after it has loaded is announced. The element cannot tell a callout added in answer to something from one added with a new view of a single-page application, so for a notice that is simply part of a new view, leave live off.

Before the element is defined

The body, the title slot and the actions are the element’s own children, so they are in the document: found by search in the page, translated, selected, and read without script. @tanrei/web/callout.css gives a callout that is not defined yet the box it will have — its paddings, its text styles, the room of its glyph and of its close button, and nothing at all for open="false" — so nothing moves when the element is defined. Until then it shows without the fill, the glyph and the close button.

  • Say the status in words. “Warning: back up first” needs nothing more; “Accessibility” on a warning needs status-label="Warning".
  • Use the statuses for what they mean everywhere. Advice is not a success.
  • Put one list of points in one callout rather than several callouts in a row.

Don’t

  • Don’t use a live callout for a note that is written into the page. Static content is not an alert.
  • Don’t nest callouts, and don’t stack several at the top of a page: notices of the same weight side by side cancel each other.
  • Don’t both move focus to an error summary and announce it. Moving focus to the summary suits a long form whose first error may be off screen; announcing it and moving focus to the first field in error suits a short one.
  • Don’t rely on the color to carry the status. In forced colors every callout is an edge and text in the text color.

Accessibility

Role
note
Labeling
A callout that is not live is a note (the note role on the web), read where it stands, named by its title or, without one, by statusLabel. A live callout is a live region instead: 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), announced once when it is shown after its page was presented and when its content changes. In both, statusLabel is exposed as text before the title and the body; the glyph is hidden. A title given as text is plain text; a heading in the title slot keeps its level. The close button's name is closeLabel. Actions are the product's own controls with their own names.
Minimum target
The hit area meets the minimum target size even when the visual size is smaller. minimum 32px · pointer 44px · spacing 8px · touch 48px

Keyboard

Keys Action
Tab Reaches, at the callout's place in the page, the links and controls of the body, then the actions, then the close button. The callout itself is not a stop, and showing one never moves focus.
Enter / Space Activates the focused close button: the callout closes and focus returns to where it was before it entered the callout (CLO-12).

Notes

  • Static content is not an alert. A callout written into a page (a note in documentation, a caution above a procedure) must not interrupt a screen reader when the page loads or be repeated as a status message: it is read in order, as the paragraph before it is. live is for what appears or changes in answer to something, and even then a callout that the page is first presented with is not announced (CLO-21).
  • A summary of a form's errors is a live callout with the error status, shown after the submit. Whether focus also moves to it is the product's decision, by its own means (the callout takes no focus by itself): moving focus to the summary suits a long form whose first error may be off screen; announcing it and moving focus to the first field in error suits a short one. Do not do both.
  • A notice for a whole page or screen (a banner: the connection is lost, the file changed on disk, a trial ends) is a callout placed by the product above that region's content, live, usually with an action or a close button. Put it where it comes first in reading order for the region it concerns.
  • WCAG 1.4.1 (use of color): a status is never told by color alone. A status form draws the status's glyph, a shape of the icon set, and the status is in the title's words or in statusLabel (CLO-20). The component cannot tell whether a title says its status, so that is the product's to check: "Accessibility" on a warning needs statusLabel "Warning"; "Warning: not published yet" does not.
  • WCAG 1.3.1 (info and relationships): the callout does not decide whether its title is a heading. Most titles are labels of a note and should stay text, or the page's outline fills with entries such as "Note". Where a callout does open a part of the page that people navigate to, the product gives a heading of the right level in the title slot.
  • WCAG 1.4.3 and 1.4.11 (contrast) in the default system: the title and the body are a role on its paired role (on-secondary-container on secondary-container, on-info-container on info-container, on-surface on a surface), each held to 4.5:1 by the token invariants. The glyph is drawn at an opacity of its color, so its contrast is that of the color as drawn: at least 3.5:1 on its own status container and on every surface, in every scheme. A link in the body is the system's link color, underlined: at least 4.6:1 on every tonal container (7.2:1 in the high-contrast schemes). The outlined look's edge is decorative.
  • Actions are the product's Buttons. A filled or a tonal Button brings its own fill and keeps its own contrast; the label of an outlined or a text Button sits on the callout's fill, where the default system's primary keeps at least 13:1 on every tonal container. A tonal Button has the fill of the tonal callout without a status and cannot be seen as a button there: use outlined or text. The focus indicator of a product's control in the body or the actions is the system's focus color, at least 4.6:1 on every tonal container of the default system.
  • A closable callout that returns on every visit is noise: when the person closes one, the product should remember it. The callout only reports the close (dismiss).
  • Do not nest callouts, and do not stack several at the top of a page: two notices of the same weight side by side cancel each other. One callout may hold a list of points.
  • Forced colors (CLO-24): a tonal callout loses its fill, so every look gains an edge in the text color. Statuses then differ by glyph and words alone.
  • Web: the body, the title slot and the actions are light-DOM children of `<tanrei-callout>`, so they stay in the document: they are found by search in the page, translated, selected and read without script, and before the element is defined they show as plain blocks in the page's own text styles, without the fill, the glyph and the close button. A title given by the `headline` attribute is not shown until the element is defined; content pages should use the title slot.
  • The callout itself has no states: nothing about its container, its text or its glyph answers a pointer, a press or focus. Its close button is a Button, and answers them as every Button does.
  • WCAG 1.4.11 (non-text contrast) and 2.4.13 (focus appearance) for the close button: in a filled look it is drawn in the tone of the fill (BTN-45), its glyph in the fill's content color (the body color, held to 4.5:1 on the fill by the token invariants) over a container of that color at comp.button.tone.container.opacity. In the default system the glyph keeps against that blend, in every status, at least 9.5:1 at rest in the light schemes and 7.7:1 in the dark ones, and at least 6.0:1 hovered, focused or pressed in every scheme; the focus indicator's outer stroke, in the same color on the callout's fill, keeps at least 12:1. In the outlined look it is the variant's own Button on the surface, whose glyph the Button's own tokens hold.

Generated from specs/callout.spec.json, tokens/sys/measure.tokens.json

What the component handles

  • It is a note (role="note"), named by its title, or by status-label where it has no title. With live it is a live region instead: role="status", or role="alert" for a warning or an error.
  • status-label is read before the title and the body as a phrase of its own (“Warning, …”) and is never drawn. It is ignored while status is default.
  • The glyph is hidden from assistive technology.
  • A title given as text is plain text. A heading in the title slot keeps its level.
  • It is not a stop in the focus order and never moves focus. Tab reaches the links and controls of the body, then the actions, then the close button.
  • When it is closed while focus is inside it, focus returns to the element that had it before it entered the callout, so the next Tab reaches what followed.
  • The close button is a Button: its hit area reaches the minimum target, and on a tonal callout its glyph and its focus ring are in the callout’s text color, which keeps at least 6:1 against the button in every status and color scheme of the default system.
  • With enlarged text everything wraps and grows; the glyph and the close button stay on the first line.
  • In forced colors every look gains an edge in the text color, and the glyph is drawn whole.
  • In a right-to-left layout the glyph moves to the right and the close button to the left, a glyph the icon set marks is mirrored, and a left-to-right title keeps its own order.
  • An unknown variant, size or status, a callout with no title and no body, a status that is not given in words, a close button without a name and a status-label without a status are reported in debug builds.

What you need to do

  • Check that each callout’s words say its status. The component cannot tell whether a title does.
  • Write status-label and close-label in the language of the page.
  • Use live only for a callout that appears or changes in answer to something.
  • Give an action a label that fits. A Button’s label is one line, so on a narrow screen with enlarged text a long label can be wider than the callout.