Message
A notice stands where its words apply: the failure beside the form that failed, the warning above the list it is about. It is not positioned and takes no box or shadow. A square mark, one control high and filled in the tone’s colour, carries the tone; the words sit on the paper beside it, each line one square, with the actions on the row below.
Message shows information on an app or page. Set form="inline", form="banner", form="toast", form="assistant" or form="app". The form describes where the message appears; tone describes what it means.
Design examples
Section titled “Design examples”Success and warning messages share a face while their tone communicates the state.
<section class="specimen">
<ds-message tone="success" heading="Proof approved" text="The printer can prepare this edition." literal></ds-message><ds-gap></ds-gap><ds-message tone="warning" heading="Check the cover image" text="The supplied image is smaller than the print size." literal dismissible><ds-button slot="actions" secondary>View image</ds-button></ds-message>
</section>
<style>
.specimen { width: 100%; min-width: 0; --cell: calc(20px * var(--sf, 1)); }
.specimen .line { width: 100%; }
.specimen .grow { flex: 1; min-width: 0; }
.specimen .muted { color: var(--ds-text-color-muted); }
.specimen .mono { --ds-typeface-regular: var(--ds-typeface-mono); font-variant-numeric: tabular-nums; }
.specimen .value { width: calc(4 * var(--cell)); text-align: right; }
.specimen .mark { display: inline-flex; align-items: flex-end; width: var(--cell); height: var(--ds-control-height); flex: 0 0 var(--cell); }
.specimen .columns { display: flex; flex-wrap: wrap; gap: var(--cell); }
.specimen .columns > * { min-width: 0; }
.specimen fieldset { border: 0; padding: 0; margin: 0; }
.specimen legend { padding: 0; }
.specimen label { display: flex; align-items: flex-end; gap: calc(var(--cell) / 2); min-height: var(--cell); }
.specimen ds-panel { height: calc(10 * var(--cell)); }
</style> Try it
Section titled “Try it”Danger is an alert, the others a status. The close button fires ds-message-dismiss and hides the notice.
<ds-message text="Changes from an earlier session are stored on this device." literal></ds-message>
<ds-message tone="success" text="Your changes are saved." literal></ds-message>
<ds-message tone="warning" heading="This list was edited somewhere else" text="Your changes are safe on this device." literal>
<ds-button slot="actions" secondary>Keep mine</ds-button>
<ds-button slot="actions" secondary>Load theirs</ds-button>
</ds-message>
<ds-message tone="danger" heading="Couldn't save" text="Check your connection and try again." literal dismissible>
<ds-button slot="actions" secondary>Try again</ds-button>
</ds-message> Use it
Section titled “Use it”<ds-message tone="danger" heading="Couldn't save" text="Check your connection and try again." dismissible> <ds-button slot="actions" secondary>Try again</ds-button></ds-message>Say what happened and what to do about it, in plain words. A notice needs a title, a text or both: a one-line message is a text alone.
| Tone | Mark | Words | Role |
|---|---|---|---|
neutral (the default) | The grey fill, with info | The text colour | status |
success | Apple green, with check | The text colour | status |
warning | Orange, with warning | The text colour | status |
danger | The danger colour, with warning | The danger colour (--ds-danger-color) | alert |
The element is a live region: it gives itself role="status", or role="alert" for danger, so a screen reader reads it when it appears. A role the page writes on the element is kept. icon names another icon from the library for the mark, such as lock for something that cannot be changed.
Dismissing
Section titled “Dismissing”dismissible adds a close button at the end of the first line, labelled with the shared word “Close”. Pressing it fires ds-message-dismiss, which bubbles and is cancelable: unless a listener cancels it, the notice hides itself. Cancel it to keep the notice, or to remove it from the page’s own state. dismiss() asks in the same way.
notice.addEventListener("ds-message-dismiss", event => { event.preventDefault(); store.dismiss(id);});Across the top
Section titled “Across the top”banner makes the notice a strip one square high across the top of its positioning context: put it in a positioned container, or at the page root. It is filled in the tone’s colour, with the mark, the title and the text on one line, the actions at the end of the line and the close button last. It uses absolute positioning and reserves no room in the flow; leave a square for it where words stand under it.
With banner, the notice is a strip at the top of this isolated preview, its action at the end of the line.
<ds-message form="banner" tone="success" text="Your changes are saved" literal><a slot="actions" class="ds-link" href="/icons/">Browse icons</a></ds-message> The close button fires ds-message-dismiss and hides the banner.
<ds-message form="banner" text="A new version is ready" literal dismissible><ds-button slot="actions" secondary>Reload</ds-button></ds-message> <ds-message form="banner" tone="success" text="Your changes are saved" literal> <a slot="actions" class="ds-link" href="/icons/">Browse icons</a></ds-message>An action is a real link or button in the actions slot.
Leading to the app
Section titled “Leading to the app”form="app" is the row at the top of a site that leads to its iPhone app: the app’s picture, its name (heading) with one line about it (text) in the muted ink under it, Open and the close. It stands in the flow above the site’s own header, on the paper, with a hairline at its foot.
form="app" at the top of the page: drawn in iOS browsers other than Safari, which shows Apple's own banner. preview draws it here on any device.
<ds-message form="app" heading="Field notes" text="Notes for the day, kept on your phone" literal src="/favicon.svg" app-id="1234567890" open="https://example.com/notes" preview></ds-message> <ds-message form="app" heading="Field notes" text="Notes for the day, kept on your phone" literal src="/app-icon.png" app-id="1234567890" open="https://example.com/notes"></ds-message>It decides for itself where it shows, before it draws, so it never pops in after the page:
| Where | What shows |
|---|---|
Safari on an iPhone or iPad, with app-id | Nothing of its own: it writes Apple’s <meta name="apple-itunes-app" content="app-id=…, app-argument=…"> and Safari draws its Smart App Banner. |
| Chrome, Firefox, Edge and iOS’s other browsers | The row. |
| A computer, Android, a page opened from the home screen, an app’s own web view | Nothing. |
| Anywhere, once closed | Nothing again in this browser (remembered under remember, one name per app by default). |
Safari reads the meta tag as the page loads, so a page that knows its app writes the tag in its own head; the element writes one only where the page did not. Without app-id (an app not yet in the App Store, with only app-url), Safari draws the row as the other browsers do.
Open is a link to the app’s store address (app-url, or the App Store’s own from app-id), which offers Open when the app is installed. With open, the link first tries that address (a universal link or the app’s own scheme, the page the person is on) and goes to the store when the page is still in view a moment later. preview draws the row on any device, for documentation.
| Attribute | What it does |
|---|---|
heading | The app’s name. |
text | One line about it. |
src | The app’s picture; the icon slot takes an element instead. |
app-id | The App Store id: Apple’s banner and the store address. |
app-url | The store address, when it is not the App Store’s own. |
open | The address that opens the app at this page. |
remember | The name the close is remembered by. |
preview | Draw it on any device. |
It is three squares high: half a square above and below two squares of content. The picture fills two squares with a picture’s corners and a hairline; the name and the line take a square each on the sixteen, cut with an ellipsis; Open (the primary button, one control high) and the close each stand on the sixteen of the square in the middle of the two, the close in a one-square cell at the end; one square at each side. Every measure follows the scale. It is a region named by the app’s name.
| Attribute | Type | Default | What it does |
|---|---|---|---|
tone | neutral, success, warning, danger | neutral | The mark’s colour and icon, and the role. |
heading | string | Empty | A title on the first line, in the medium face. |
text | string | Empty | The text. The default slot takes its place. |
literal | boolean | false | Show heading and text as written, never looked up. |
icon | string | The tone’s | Another icon for the mark. |
dismissible | boolean | false | Show the close button. |
banner | boolean | false | A strip across the top of its positioning context. |
form | inline, banner, toast, assistant, app | inline | Where the message appears; app is the row leading to the site’s app (see above). |
| Slot | What it holds |
|---|---|
| Default | The text, when it needs more than words. |
heading | The title. |
icon | An icon of the page’s own inside the mark. |
actions | Buttons or links, on the row under the words. |
Measures
Section titled “Measures”The mark is one control high (16px) in a one-square cell, so the words start one square in; the close button takes a one-square cell at the end of the first line. Each line of the title and the text is one square, and the actions share one row with half a square between them. The notice fills the width it is given. Tones set --ds-message-mark, --ds-message-mark-ink and --ds-message-ink.
import { Message } from "@0001-labs/design-system-1/DS1/react/message";
<Message tone="warning" heading="Edited somewhere else" onDismiss={() => setShown(false)} actions={<><Button secondary onClick={keepMine}>Keep mine</Button><Button secondary onClick={loadTheirs}>Load theirs</Button></>}> Your changes are safe on this device.</Message>Message renders ds-message with its words and actions in the light DOM and its role in the markup, so the server’s HTML is already a live region. onDismiss shows the close button and leaves removing the notice to the product. Register ds-message in the browser before hydration.
The app banner takes the same props: <Message form="app" heading="Field notes" text="Notes for the day, kept on your phone" src="/app-icon.png" appId="1234567890" open={deepLink} />.
Assistant
Section titled “Assistant”An assistant message stays in the page. Supply its identity through heading or the icon slot, its response as content, and its next steps through actions. The app owns conversation state and requests.
The same Message face, with an identity and actions supplied by the app.
<ds-message form="assistant" heading="Assistant" text="Your draft is ready to review." literal><ds-button slot="actions" secondary>Review draft</ds-button></ds-message> Toast messages can be persistent or timed. Persistent messages stay until dismissed (duration: Infinity, the default); timed messages close automatically after their duration in milliseconds, such as duration: 4000. Both have a close button by default. The same choice applies to success and error messages after a promise finishes. The toast form uses the existing Sonner-based queue, gestures and announcements. Put one host in the app; message() sends messages to it.
<ds-message form="toast" position="bottom-right" close-button></ds-message>import { message } from "@0001-labs/design-system-1";message("Review required", { duration: Infinity });message.success("Changes saved", { duration: 4000 });message.error("Could not save");message.promise(save(), { loading: "Saving", success: "Saved", error: "Could not save" });message.dismiss();Persistent stays until dismissed. Timed closes after four seconds; hover to pause its timer. Close either with its close button or a swipe.
<ds-message form="toast" position="bottom-right" close-button></ds-message>
<ds-row>
<ds-button secondary data-toast="persistent">Persistent</ds-button>
<ds-button secondary data-toast="timed">Timed</ds-button>
<ds-button secondary data-toast="success">Success</ds-button>
<ds-button secondary data-toast="error">Error</ds-button>
<ds-button secondary data-toast="action">Action</ds-button>
<ds-button secondary data-toast="promise">Promise</ds-button>
</ds-row> import { message } from "@0001-labs/design-system-1";
message("Review required", { duration: Infinity });
message.success("Changes saved", { duration: 4000 });
message.success("Changes saved");
message.error("Could not save");
message("Item archived", { action: { label: "Undo", onClick: () => restore() } });
message.promise(save(), { loading: "Saving", success: "Saved", error: "Could not save" }); message() supports the existing toast() options, including descriptions, actions, IDs, duration and promise updates. In React, import Message and message from DS1/react/message; register ds-message in the browser before hydration.
| Attribute | Default | What it does (as in Sonner) |
|---|---|---|
position | bottom-right | top-left, top-center, top-right, bottom-left, bottom-center or bottom-right. |
expand | Off | Keeps the toasts spread out instead of stacked. |
visible-toasts | 3 | How many toasts show at once. |
duration | Infinity | Use Infinity to stay until dismissed, or milliseconds (for example 4000) to close automatically. |
close-button | On | Adds a close button to every toast. |
rich-colors | Off | Fills success, info, warning and error toasts with their colour. |
contained | Off | Not in Sonner: draws the toasts inside the nearest positioned parent, such as a panel’s status area, instead of the window. |
label | Messages | The region’s accessible name; pass the product’s own words. |
close-label | Close | The close button’s accessible name. |
A toast’s options
Section titled “A toast’s options”id, description, duration (Infinity keeps it), icon (a Design system 1
icon name), action and cancel ({ label, onClick }; the toast closes after
the click unless onClick calls preventDefault(); with href, not in Sonner,
the button is a link and closes the toast as it is followed), dismissible,
closeButton, important (announced assertively; an error is unless it says
false), onDismiss and
onAutoClose. message.promise() also takes loading, success, error (text,
or a function of the result) and finally.
Behaviour
Section titled “Behaviour”As in Sonner: the newest toast is in front, the next two peek out behind it, and hovering or focusing the stack spreads them out and pauses their time, as does holding a toast or hiding the tab. A swipe towards the edge dismisses a toast; Alt+T moves focus to the toasts and Escape lets go. Loading toasts stay until they are updated or dismissed.
Where it departs from Sonner
Section titled “Where it departs from Sonner”A toast is drawn as Design system 1’s label, not as Sonner’s card: an ink chip
one control high for each line of words, in the tooltip’s colours (black with
white words, turning over in dark mode), padded 4px, with no line and no
shadow. It is as wide as its words, up to sixteen squares. Actions and Cancel
are words of the same size, underlined. The pile advances one square per
toast, a control and the rest of the square, and stands one square clear of
the edge and the phone’s safe area. Only transform and opacity animate; a
toast stacked behind takes the front one’s height at once. Swiping works
towards the vertical edge only. Moving the element to another parent keeps its
toasts and the time each had left. An error is announced assertively unless it
says important: false.
Set --ds-toast-width, --ds-toast-background and --ds-toast-color on
ds-message with shared tokens to fit a surface. Copy message through Sync 1.
Compatibility
Section titled “Compatibility”ds-notice, Notice, ds-toast, Toaster and toast() remain supported for existing apps. They are compatibility entry points for Message. The native CSS classes remain ds-notice and ds-notice__*.