Over the page
What floats over the page: popovers, menus, tooltips, dialogs, drawers and toasts. Each sits in the browser's top layer, so opening it moves nothing beneath, and each has the one shadow v2 allows.
Each section is built from the component's spec in mcp/components.mjs, the same spec the design system MCP gives agents.
Popover
A small panel tied to a button: about this, a filter, a picker. It opens over the page, so nothing moves.
Each bar is one possible next token. Its length and its number are the model's probability for it.
<button class="ds-button" type="button" popovertarget="ex-popover">About this view</button>
<div class="ds-popover" id="ex-popover" popover>
<p>Each bar is one possible next token. Its length and its number are the model's probability for it.</p>
</div>
Rules
- The native popover attribute, opened by a button with popovertarget: a click outside or Escape closes it, and the browser tells screen readers that the button's popover is open.
- It sits in the top layer, so opening it moves nothing.
- Where anchor positioning works it opens under its button; elsewhere, in the middle of the window.
- Short: a few lines, a short list or a small form. A long explanation or a decision is a dialog.
- It carries the one shadow, for things that float; panels have none.
Variants and states
States: closed and open.
Accessibility
| Element | <div class="ds-popover" popover> and the <button popovertarget> that opens it |
|---|---|
| Keys | Enter Space Escape |
| Notes | The browser gives the button its expanded state; focus stays on the button until you move into the popover. |
Anatomy
| The whole | <div class="ds-popover" id="…" popover> |
|---|---|
| Opener | <button popovertarget="…">, just before it |
| Content | a few lines, a short list or a small form |
Right after the button that opens it.
Roles it uses
| Background | --ds-surface |
|---|---|
| Border | --ds-rule |
| Radius | --ds-radius-panel |
| Shadow | --ds-shadow-float |
| Padding | --ds-space-4 |
Related: Dialog, Button and Field. Used in Top bar, forms and Figure.
Tooltip
A short name or value, on hover and on keyboard focus: an icon button's name, a point on a chart.
<span class="ds-tooltip-wrap">
<button class="ds-icon-button" type="button" aria-labelledby="ex-tip-full"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">…</svg></button>
<span class="ds-tooltip" id="ex-tip-full" role="tooltip" popover="manual">Full screen</span>
</span>
<span class="ds-tooltip-wrap">
<button class="ds-button" type="button" aria-describedby="ex-tip-reset">Reset</button>
<span class="ds-tooltip" id="ex-tip-reset" role="tooltip" popover="manual">Back to temperature 1 and the whole vocabulary.</span>
</span>
Rules
- Load
/v2/tooltip.js: it shows on hover and on keyboard focus, stays while the pointer moves onto it, and hides on Escape (WCAG 1.4.13). - Its text is always in the page, named by the control's
aria-labelledby(the tooltip is its name) oraria-describedby(it adds to the name), so a screen reader has it whether it shows or not. - Never the title attribute, which a keyboard never sees.
- A few words: a name or a value. Anything longer, or anything to act on, is a popover.
- Nothing hovers on a phone: an icon button there still needs an icon people know.
- It opens in the top layer, above its control and inside the window, so it moves nothing.
- Corners signal function: this is something you read, so it stays square (
--ds-radius-read).
Variants and states
| Name | aria-labelledby on the control: the tooltip is its name, for an icon button. |
|---|---|
| Description | aria-describedby on the control: the tooltip adds to the name it already has. |
States: hidden and shown.
Accessibility
| Element | <span class="ds-tooltip" role="tooltip" popover="manual">, after its control in a .ds-tooltip-wrap |
|---|---|
| Needs | aria-labelledby or aria-describedby on the control, naming the tooltip |
| Keys | Tab Escape |
Anatomy
| The whole | <span class="ds-tooltip-wrap"> |
|---|---|
| Control | the button or element it is about, first |
| Tooltip | <span class="ds-tooltip" id="…" role="tooltip" popover="manual"> |
On icon buttons in the top bar's tools; on a chart's points.
Roles it uses
| Background | --ds-surface |
|---|---|
| Border | --ds-rule |
| Shadow | --ds-shadow-float |
| Font size | --ds-type-label |
| Padding | --ds-space-1 |
Related: Popover and Icon button. Used in Top bar and Figure.
Dialog
A window over the page: information, an editor, or a confirmation before something that cannot be undone.
<button class="ds-button" type="button" data-ds-opens="ex-dialog">Clear the run</button>
<dialog class="ds-dialog" id="ex-dialog" role="alertdialog" aria-labelledby="ex-dialog-title" aria-describedby="ex-dialog-text">
<h2 id="ex-dialog-title">Clear this run?</h2>
<p id="ex-dialog-text">Its 12 answers and their probabilities are deleted. This cannot be undone.</p>
<form class="ds-dialog-actions" method="dialog">
<button class="ds-button ds-button--primary" value="clear">Clear the run</button>
<button class="ds-button" value="keep" autofocus>Keep it</button>
</form>
</dialog>
Rules
- The native
<dialog>opened withshowModal(): focus moves in and stays, Escape closes it, and focus returns to what opened it. Load/v2/dialog.jsand give the opening buttondata-ds-opens="<the dialog's id>", or callshowModal()yourself. - A
<form method="dialog">closes it: the value of the button pressed says which, with no script. - A confirmation is
role="alertdialog": its title asks the question and its text names what will happen ("Its 12 answers are deleted"). - The primary action first, named for what it does (Clear the run, not OK); focus starts on the safe choice (autofocus).
- Never the browser's own
alert()orconfirm(). - It sits in the top layer, so opening it moves nothing.
Variants and states
| Default | <dialog class="ds-dialog" aria-labelledby="…">: information or an editor. |
|---|---|
| Confirm | role="alertdialog" with aria-describedby: a confirmation that names what will happen; focus starts on the safe choice. |
States: closed and open.
Accessibility
| Element | <dialog class="ds-dialog"> opened with showModal() |
|---|---|
| Needs | aria-labelledby naming its title; role="alertdialog" and aria-describedby naming its text, for a confirmation |
| Keys | Tab Escape |
| Notes | showModal() makes the rest of the page inert while it is open, and returns focus when it closes. |
Anatomy
| The whole | <dialog class="ds-dialog" id="…" aria-labelledby="…"> |
|---|---|
| Title | <h2 id="…">: a question, for a confirmation |
| Text | <p id="…">: what will happen |
| Actions | <form class="ds-dialog-actions" method="dialog"> holding the buttons, the primary first |
Opened by a button with data-ds-opens; the dialog itself may sit anywhere in the page.
Roles it uses
| Background | --ds-surface |
|---|---|
| Border | --ds-rule |
| Radius | --ds-radius-panel |
| Shadow | --ds-shadow-float |
| Scrim | --ds-scrim |
| Width | --ds-measure |
| Padding | --ds-space-6 |
| Title | --ds-type-h3 |
Drawer
A panel at the window's edge, over the page: a phone's navigation, settings, an inspector.
<button class="ds-button" type="button" data-ds-opens="ex-drawer">Settings</button>
<dialog class="ds-drawer" id="ex-drawer" aria-labelledby="ex-drawer-title" closedby="any">
<div class="ds-drawer-head">
<h2 id="ex-drawer-title">Settings</h2>
<form method="dialog"><button class="ds-button ds-button--ghost">Close</button></form>
</div>
<div class="ds-slider">
<label class="ds-label" for="ex-drawer-temp">Temperature</label>
<output for="ex-drawer-temp">1</output>
<input id="ex-drawer-temp" type="range" min="0" max="2" step="0.1" value="1">
</div>
<div class="ds-slider">
<label class="ds-label" for="ex-drawer-topk">Top K</label>
<output for="ex-drawer-topk">40</output>
<input id="ex-drawer-topk" type="range" min="1" max="100" step="1" value="40" aria-valuetext="40 tokens">
</div>
</dialog>
Rules
- A native
<dialog>opened withshowModal(), as a dialog is: focus moves in and stays, Escape closes it and focus returns. Load/v2/dialog.jsand open it from a button withdata-ds-opens. closedby="any"closes it on a click outside, where the browser knows the attribute; Escape and its Close button work everywhere.- Its head holds its title and a Close button (a word; v2 has no close icon) in a
<form method="dialog">. - An inspector that stays open on a wide screen is a column of the page's layout, not a drawer; the drawer is for when there is no room for one.
- It sits in the top layer, so opening it moves nothing.
- Corners signal function: this is something you read, so it stays square (
--ds-radius-read).
Variants and states
| End | class="ds-drawer": at the right edge; settings, an inspector. |
|---|---|
| Start | class="ds-drawer ds-drawer--start": at the left edge; a phone's navigation. |
States: closed and open.
Accessibility
| Element | <dialog class="ds-drawer"> opened with showModal() |
|---|---|
| Needs | aria-labelledby naming its title |
| Keys | Tab Escape |
| Notes | showModal() makes the page behind it inert and returns focus when it closes. |
Anatomy
| The whole | <dialog class="ds-drawer" id="…" aria-labelledby="…" closedby="any"> |
|---|---|
| Head | <div class="ds-drawer-head">: the <h2> title and a <form method="dialog"> with Close |
| Body | fields, sliders, a list of links |
Opened by a button with data-ds-opens, often in the top bar's tools.
Roles it uses
| Background | --ds-surface |
|---|---|
| Border | --ds-rule |
| Shadow | --ds-shadow-float |
| Scrim | --ds-scrim |
| Padding | --ds-space-5 |
| Title | --ds-type-h3 |
Related: Dialog, Popover, Slider, Field and Button. Used in pages.
Toast
Word that something finished elsewhere on the page: saved, added, failed. A notice that floats at the window's foot.
<button class="ds-button" type="button" data-ds-toast="Your prompt is kept for this workshop." data-ds-toast-kind="ok" data-ds-toast-word="Saved">Save the prompt</button>
Rules
- Load
/v2/toast.js: it makes the page's.ds-toastslive region when the page loads, so the first toast is read; dsToast(text, { kind, word }) adds one, and so does a button withdata-ds-toast. - A toast is a notice: its first word says the kind (Saved, Added, Error), and the color only repeats it.
- It stays six seconds, and longer while the pointer or focus is on it; an error stays until it is dismissed. Each has a Dismiss button.
- Only for word from elsewhere on the page. What happens here, where someone is looking, is a notice in its own place.
- Never the only record of an error: say it where it happened too, in a field's note or a notice.
- Fixed at the window's foot, so it moves nothing.
Variants and states
| Note | dsToast(text): news, neither good nor bad. |
|---|---|
| Ok | dsToast(text, { kind: 'ok', word: 'Saved' }): done. |
| Warn | dsToast(text, { kind: 'warn' }): done, with a catch. |
| Bad | dsToast(text, { kind: 'bad' }): it failed; role="alert", and it stays until dismissed. |
States: shown, paused and gone.
Accessibility
| Element | <div class="ds-toasts" aria-live="polite"> holding notices; an error is role="alert" |
|---|---|
| Needs | the live region in the page before the first toast (toast.js makes it) |
| Keys | Tab Enter |
| Notes | It pauses while the pointer or focus is on it (WCAG 2.2.1). |
Anatomy
| The whole | <div class="ds-toasts" aria-live="polite">, made by toast.js |
|---|---|
| Toast | <div class="ds-notice ds-notice--ok">, its kind word first |
| Dismiss | <button class="ds-button ds-button--ghost">Dismiss</button> |
At the window's foot; newer toasts below older ones.
Roles it uses
| Background | --ds-surface |
|---|---|
| Border | --ds-rule |
| Shadow | --ds-shadow-float |
| Layer | --ds-layer-float |
Related: Notice and Copy button. Used in pages.