| Version | Tag | Status | API |
|---|---|---|---|
| v1 | <oc-snackbar-v1> |
Stable, allowed for generation | SnackbarV1 |
Overview (v1)
Source: ./src/components/snackbar/v1/Overview.mdx
Snackbar
The snackbar component allows you to display any Snackbar from the OTTO components library.
Important
The snackbar component is not compatible with the deprecated
pl_grid!
Default variation
Story Default:
<oc-snackbar-v1 id="default-snackbar" message="Label"></oc-snackbar-v1>
Configuration
The snackbar component offers setting a message text and an optional action. This component is configurable, allowing you to tailor its features and appearance to your specific needs. To explore all the available options and adjust the component, use the component configurator and see the changes affect the component in real-time.
Usage guidelines
Before integrating the snackbar component into your project, make sure you have correctly installed the OTTO components package. Look through the variations page for examples of possible component variations.
Creating snackbars via the Nexus API
The snackbarV1.push Nexus API method creates and displays a snackbar programmatically, without requiring markup in the DOM.
Pass a classNames option (a space-separated string of CSS class names) to apply custom styling to the created oc-snackbar-v1 element, the same way you would set a class attribute on a markup-based snackbar.
Behavior
The snackbar uses an auto dismiss per default. This closes the snackbar after a fixed duration (5000ms). The timer pauses as long as a user hovers the snackbar. Please make sure that you disable this behavior when you place actions inside the snackbar to ensure that users are not stressed to get there.
Note
When using the keyboard to open the snackbar and then tabbing to the close icon, closing the snackbar will move focus back to the root of the page.
Consider managing focus manually if you want to return focus to the triggering element or another logical position for your use case.
Accessibility
The snackbar is using srSpeak internally (with a assertive setting) so that the message is read to the user immediately.
To avoid putting users under time pressure, the snackbar remains visible until dismissed manually. This ensures users have enough time to read the message and interact with any available actions.
If you add actions to a snackbar, carefully consider which actions to include. Only use optional or informational actions — do not place critical actions inside the snackbar.
Focus order
By default, the snackbar is inserted into the DOM at the position where you place it in the code.
- Place the snackbar immediately after the triggering element (such as a button) in the DOM so that focus moves to the snackbar right after the trigger.
- When creating the snackbar programmatically or interacting with it via the Nexus API, ensure you insert the snackbar at an appropriate place in the DOM to maintain a logical and accessible focus order for your use case.
Further reading
Configuration (v1)
Source: ./src/components/snackbar/v1/Configuration.mdx
Snackbar configuration
Configure the snackbar component with the controls below and see the changes live in the preview canvas. Click the Show code button within the preview canvas to see the source code for the current component configuration.
Story Default:
<oc-snackbar-v1 id="default-snackbar" message="Label"></oc-snackbar-v1>
Interactive configurator (Storybook controls); every option is listed in the API section of this file.
API v1
Source: ./src/components/snackbar/v1/SnackbarV1.API.g.mdx
Snackbar v1 API
API: <oc-snackbar-v1> (SnackbarV1)
The snackbar component allows users to display a dismissable popup message.
Attributes / properties
| Attribute | Type | Default | Required | Description |
|---|---|---|---|---|
id |
string |
"computed" |
no | Sets the ID of the snackbar instance. The ID must be globally unique. If not provided, a random ID will be generated. |
message |
string |
yes | Sets the text that is displayed in the snackbar. Accepts plain text only. | |
auto-dismiss |
"true" | "false" |
true |
no | Sets whether the snackbar should be closed automatically after a certain duration. |
open |
boolean |
false |
no | Sets the visibility state of the snackbar. Set to true to open the snackbar. Has the same effect as using the open or close method. |
force-replace-existing-instance |
boolean |
false |
no | Set to true to perform a DOM query that removes all existing snackbar instances with the same ID. Use in scenarios when a fragment update renders a snackbar instance with the same ID as an existing one, but with different content. |
Slots
| Slot | Required | Description |
|---|---|---|
actions |
no | Holds the link components that provide interactions to the user. Example: <oc-link-v2 variant="underlined" href="#" slot="actions">Action 1</oc-link-v2> |
Events
| Event | Detail type | Description |
|---|---|---|
oc-snackbar-open |
CustomEvent<null> |
Dispatched once the snackbar instance is being opened, but before it becomes visible. If not cancelled, then the instance will become visible and start animation. |
oc-snackbar-close |
CustomEvent<null> |
The event will be fired immediately after close is requested, and before any transitions or animations are completed. |
oc-open |
CustomEvent<null> |
Dispatched once the snackbar instance is being opened, but before it becomes visible. If not cancelled, then the instance will become visible and start animation. |
oc-close |
CustomEvent<null> |
The event will be fired immediately after close is requested, and before any transitions or animations are completed. |
oc-property-change |
OcSnackbarV1Events["oc-property-change"] |
Whenever a property value changes, this event triggers. Use this event to track all property changes within the component. Refer to the Events documentation for more information. |
oc-mount |
{ component: string; } |
Fired when the component is mounted to the DOM. The event is fired when the onMount hook of the component is called by the runtime. |
oc-unmount |
{ component: string; } |
Fired when the component is unmounted from the DOM. The event is fired when the function returned by the onMount hook of the component is called by the runtime. |
Methods
close:() => voidCloses the snackbar. Provides the same effect as setting the attribute
opentofalse. This method provides the same effect as clicking the close button.Returns:
voidExample:
const snackbar = document.querySelector("oc-snackbar-v1"); snackbar.close();
CSS custom properties
| Custom property | Default | Description |
|---|---|---|
--background-color |
undefined |
Sets the custom background color |
Variations (v1)
Source: ./src/components/snackbar/v1/Variations.mdx
Variations
Listed below are the most common variations of the snackbar component as well as specific component variations for different use cases.
You can explore all available options using the component configurator, adjust the component to your needs, and see the changes live in a preview canvas.
Default
Story: components-snackbar-variations--default · tags: components
The default configuration
<oc-snackbar-v1 id="default-snackbar" message="Label"></oc-snackbar-v1>
Long message
Story: components-snackbar-variations--long-message · tags: components
Snackbar with a long message
Args: id=snackbar-with-long-message, message=(see snippet)
<h2>Snackbar with long message</h2>
<br />
<p>
At breakpoint S, the snackbar has a fixed width with spacing to the side whereas from M
upwards, it uses as much space as the content needs.
</p>
<p>In either case, the text will be line-wrapped once the outer limits are reached.</p>
<oc-button-v1 id="open" size="50">Open snackbar</oc-button-v1>
<oc-snackbar-v1 id="snackbar-with-long-message" message="Lorem ipsum dolor sit amet, consete sadipscing elitr. Lorem ipsum dolor sit amet, consete sadipscing elitr. Lorem ipsum dolor sit amet, consete sadipscing elitr."> ${unsafeHTML(actionsSlot)} </oc-snackbar-v1>
<script>
(() => {
const openButton = document.getElementById("open");
openButton.addEventListener("click", openSnackbar);
function openSnackbar() {
const snackbarElement = document.getElementById("snackbar-with-long-message");
snackbarElement.open = true;
}
})();
</script>
Story source (TypeScript, verbatim from Storybook)
{
name: "Long message",
argTypes: hideControlsBadge(Metadata),
parameters: {
controls: {
disabled: true
}
},
args: {
id: "snackbar-with-long-message",
message: "Lorem ipsum dolor sit amet, consete sadipscing elitr. Lorem ipsum dolor sit amet, consete sadipscing elitr. Lorem ipsum dolor sit amet, consete sadipscing elitr."
},
render({
actionsSlot,
...props
}) {
return html`
<h2>Snackbar with long message</h2>
<br />
<p>
At breakpoint S, the snackbar has a fixed width with spacing to the side whereas from M
upwards, it uses as much space as the content needs.
</p>
<p>In either case, the text will be line-wrapped once the outer limits are reached.</p>
<oc-button-v1 id="open" size="50">Open snackbar</oc-button-v1>
<oc-snackbar-v1 ${spread(props)}> ${unsafeHTML(actionsSlot)} </oc-snackbar-v1>
<script>
(() => {
const openButton = document.getElementById("open");
openButton.addEventListener("click", openSnackbar);
function openSnackbar() {
const snackbarElement = document.getElementById("snackbar-with-long-message");
snackbarElement.open = true;
}
})();
</script>
`;
}
}
With action
Story: components-snackbar-variations--with-action · tags: components
Snackbar with action slot
Args: id=snackbar-with-action, message=Lorem ipsum dolor sit amet, consete sadipscing elitr., actionsSlot=<oc-link-v2 variant="inverted" slot="actions" as-button>Action</oc-link-v2>
<h2>Snackbar with action link</h2>
<br />
<p>
Provide actions via the actions slot. The slot content is placed next to the text message.
</p>
<p>
You can use any kind of content as actions but need to take care of the styling yourself.
</p>
<oc-button-v1 id="open" size="50">Open snackbar</oc-button-v1>
<oc-snackbar-v1 id="snackbar-with-action" message="Lorem ipsum dolor sit amet, consete sadipscing elitr."> <oc-link-v2 variant="inverted" slot="actions" as-button>Action</oc-link-v2> </oc-snackbar-v1>
<script>
(() => {
const openButton = document.getElementById("open");
openButton.addEventListener("click", openSnackbar);
function openSnackbar() {
const snackbarElement = document.getElementById("snackbar-with-action");
snackbarElement.open = true;
}
})();
</script>
Story source (TypeScript, verbatim from Storybook)
{
name: "With action",
argTypes: hideControlsBadge(Metadata),
parameters: {
controls: {
disabled: true
}
},
args: {
id: "snackbar-with-action",
message: "Lorem ipsum dolor sit amet, consete sadipscing elitr.",
actionsSlot: `<oc-link-v2 variant="inverted" slot="actions" as-button>Action</oc-link-v2>`
},
render({
actionsSlot,
...props
}) {
return html`
<h2>Snackbar with action link</h2>
<br />
<p>
Provide actions via the actions slot. The slot content is placed next to the text message.
</p>
<p>
You can use any kind of content as actions but need to take care of the styling yourself.
</p>
<oc-button-v1 id="open" size="50">Open snackbar</oc-button-v1>
<oc-snackbar-v1 ${spread(props)}> ${unsafeHTML(actionsSlot)} </oc-snackbar-v1>
<script>
(() => {
const openButton = document.getElementById("open");
openButton.addEventListener("click", openSnackbar);
function openSnackbar() {
const snackbarElement = document.getElementById("snackbar-with-action");
snackbarElement.open = true;
}
})();
</script>
`;
}
}
Custom Background Color
Story: components-snackbar-variations--custom-background-color · tags: components
Snackbar with action slot & custom background-color
Args: id=snackbar-with-action, message=Lorem ipsum dolor sit amet, consete sadipscing elitr., actionsSlot=<oc-link-v2 variant="inverted" slot="actions" as-button>Action</oc-link-v2>, --background-color=var(--oc-semantic-color-background-strong-warm-red)
<h2>Snackbar with custom background-color</h2>
<oc-button-v1 id="open" size="50">Open snackbar</oc-button-v1>
<oc-snackbar-v1 id="snackbar-with-action" message="Lorem ipsum dolor sit amet, consete sadipscing elitr." style="--background-color: var(--oc-semantic-color-background-strong-warm-red)" class="${className}">
<oc-link-v2 variant="inverted" slot="actions" as-button>Action</oc-link-v2>
</oc-snackbar-v1>
<script>
(() => {
const openButton = document.getElementById("open");
openButton.addEventListener("click", openSnackbar);
function openSnackbar() {
const snackbarElement = document.getElementById("snackbar-with-action");
snackbarElement.open = true;
}
})();
</script>
${css}
Story source (TypeScript, verbatim from Storybook)
{
name: "Custom Background Color",
args: {
id: "snackbar-with-action",
message: "Lorem ipsum dolor sit amet, consete sadipscing elitr.",
actionsSlot: `<oc-link-v2 variant="inverted" slot="actions" as-button>Action</oc-link-v2>`,
"--background-color": "var(--oc-semantic-color-background-strong-warm-red)"
},
render({
actionsSlot,
...props
}) {
const {
css,
className
} = cssVariablesExample(props);
return html`
<h2>Snackbar with custom background-color</h2>
<oc-button-v1 id="open" size="50">Open snackbar</oc-button-v1>
<oc-snackbar-v1 ${spread(props)} class="${className}">
${unsafeHTML(actionsSlot)}
</oc-snackbar-v1>
<script>
(() => {
const openButton = document.getElementById("open");
openButton.addEventListener("click", openSnackbar);
function openSnackbar() {
const snackbarElement = document.getElementById("snackbar-with-action");
snackbarElement.open = true;
}
})();
</script>
${css}
`;
}
}
Demo: programmatically created snackbar
Story: components-snackbar-variations--programmatic-snackbar · tags: components
A demo showcasing how to programmatically create a snackbar and open it.
<br />
<h2>Demo: programmatically created snackbar</h2>
<br />
<p>
<oc-text-field-v1 id="snackbar-message" name="message" value="My snackbar message"
>Message
</oc-text-field-v1>
</p>
<div id="snackbar-container" style="display: flex; gap: 10px;">
<oc-button-v1 id="create" size="50">Create snackbar</oc-button-v1>
<oc-button-v1 id="open" size="50" disabled>Open snackbar</oc-button-v1>
<oc-button-v1 id="open-nexus" size="50" disabled>Open snackbar via Nexus</oc-button-v1>
</div>
<br />
<p>
You can programmatically create a new snackbar element by creating a new snackbar element
and attaching it to the document.
</p>
<p>For example:</p>
<br />
<pre>
function createSnackbar() {
const snackbarElement = document.createElement("oc-snackbar-v1");
snackbarElement.message = "My snackbar message";
snackbarElement.open = false;
snackbarElement.id = "prog-snackbar";
document.querySelector("body").append(snackbarElement);
}
function openSnackbar() {
const snackbarElement = document.getElementById("prog-snackbar");
snackbarElement.open = true;
}
function openSnackbarNexus() {
otto.components.snackbarV1.open("prog-snackbar");
}
</pre>
<script>
(() => {
const SNACKBAR_ID = "prog-snackbar";
const createButton = document.getElementById("create");
const openButton = document.getElementById("open");
const openNexusButton = document.getElementById("open-nexus");
const snackbarMessage = document.getElementById("snackbar-message");
createButton.addEventListener("click", createSnackbar);
openButton.addEventListener("click", openSnackbar);
openNexusButton.addEventListener("click", openSnackbarNexus);
function createSnackbar() {
const snackbarElement = document.createElement("oc-snackbar-v1");
const previousSnackbarElement = document.getElementById(SNACKBAR_ID);
if (previousSnackbarElement) {
previousSnackbarElement.remove();
}
snackbarElement.message = snackbarMessage.value;
snackbarElement.id = SNACKBAR_ID;
document.getElementById("snackbar-container").append(snackbarElement);
createButton.disabled = true;
openButton.disabled = false;
openNexusButton.disabled = false;
}
function openSnackbar() {
const snackbarElement = document.getElementById(SNACKBAR_ID);
snackbarElement.open = true;
}
function openSnackbarNexus() {
otto.components.snackbarV1.open("prog-snackbar");
}
})();
</script>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: programmatically created snackbar",
argTypes: hideControlsBadge(Metadata),
parameters: {
controls: {
disabled: true
}
},
render() {
return html`
<br />
<h2>Demo: programmatically created snackbar</h2>
<br />
<p>
<oc-text-field-v1 id="snackbar-message" name="message" value="My snackbar message"
>Message
</oc-text-field-v1>
</p>
<div id="snackbar-container" style="display: flex; gap: 10px;">
<oc-button-v1 id="create" size="50">Create snackbar</oc-button-v1>
<oc-button-v1 id="open" size="50" disabled>Open snackbar</oc-button-v1>
<oc-button-v1 id="open-nexus" size="50" disabled>Open snackbar via Nexus</oc-button-v1>
</div>
<br />
<p>
You can programmatically create a new snackbar element by creating a new snackbar element
and attaching it to the document.
</p>
<p>For example:</p>
<br />
<pre>
function createSnackbar() {
const snackbarElement = document.createElement("oc-snackbar-v1");
snackbarElement.message = "My snackbar message";
snackbarElement.open = false;
snackbarElement.id = "prog-snackbar";
document.querySelector("body").append(snackbarElement);
}
function openSnackbar() {
const snackbarElement = document.getElementById("prog-snackbar");
snackbarElement.open = true;
}
function openSnackbarNexus() {
otto.components.snackbarV1.open("prog-snackbar");
}
</pre>
<script>
(() => {
const SNACKBAR_ID = "prog-snackbar";
const createButton = document.getElementById("create");
const openButton = document.getElementById("open");
const openNexusButton = document.getElementById("open-nexus");
const snackbarMessage = document.getElementById("snackbar-message");
createButton.addEventListener("click", createSnackbar);
openButton.addEventListener("click", openSnackbar);
openNexusButton.addEventListener("click", openSnackbarNexus);
function createSnackbar() {
const snackbarElement = document.createElement("oc-snackbar-v1");
const previousSnackbarElement = document.getElementById(SNACKBAR_ID);
if (previousSnackbarElement) {
previousSnackbarElement.remove();
}
snackbarElement.message = snackbarMessage.value;
snackbarElement.id = SNACKBAR_ID;
document.getElementById("snackbar-container").append(snackbarElement);
createButton.disabled = true;
openButton.disabled = false;
openNexusButton.disabled = false;
}
function openSnackbar() {
const snackbarElement = document.getElementById(SNACKBAR_ID);
snackbarElement.open = true;
}
function openSnackbarNexus() {
otto.components.snackbarV1.open("prog-snackbar");
}
})();
</script>
`;
}
}
Demo: programmatically pushed snackbar
Story: components-snackbar-variations--programmatic-snackbar-push · tags: components
A demo showcasing how to programmatically push a snackbar via the Nexus API
<br />
<h2>Demo: programmatically pushed snackbar</h2>
<br />
<p>
You can programmatically push a new snackbar element without creating a new element. Useful
for quick notifications that don't require a custom element instance with specific
configuration (e.g. id or actions).
</p>
<p>For example:</p>
<br />
<pre>
function pushSnackbarNexus() {
otto.components.snackbarV1.push("pushed-snackbar", { actions: "<oc-link-v2 slot="actions" variant="inverted" >Action</oc-link-v2>" });
}
</pre>
<oc-button-v1 id="push-nexus" size="50">Push snackbar via Nexus</oc-button-v1>
<script>
(() => {
const openNexusButton = document.getElementById("push-nexus");
openNexusButton.addEventListener("click", openSnackbarNexus);
function openSnackbarNexus() {
otto.components.snackbarV1.push("pushed-snackbar", {
actions: "<oc-link-v2 slot='actions' variant='inverted'>Action</oc-link-v2>",
});
}
})();
</script>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: programmatically pushed snackbar",
argTypes: hideControlsBadge(Metadata),
parameters: {
controls: {
disabled: true
},
chromatic: {
hideInChromatic: true
}
},
render() {
return html`
<br />
<h2>Demo: programmatically pushed snackbar</h2>
<br />
<p>
You can programmatically push a new snackbar element without creating a new element. Useful
for quick notifications that don't require a custom element instance with specific
configuration (e.g. id or actions).
</p>
<p>For example:</p>
<br />
<pre>
function pushSnackbarNexus() {
otto.components.snackbarV1.push("pushed-snackbar", { actions: "<oc-link-v2 slot="actions" variant="inverted" >Action</oc-link-v2>" });
}
</pre>
<oc-button-v1 id="push-nexus" size="50">Push snackbar via Nexus</oc-button-v1>
<script>
(() => {
const openNexusButton = document.getElementById("push-nexus");
openNexusButton.addEventListener("click", openSnackbarNexus);
function openSnackbarNexus() {
otto.components.snackbarV1.push("pushed-snackbar", {
actions: "<oc-link-v2 slot='actions' variant='inverted'>Action</oc-link-v2>",
});
}
})();
</script>
`;
}
}
Demo: pushed snackbar with custom CSS
Story: components-snackbar-variations--programmatic-snackbar-push-with-custom-class · tags: components
A demo showcasing how to apply custom CSS classes to a snackbar that is pushed via the Nexus API
<br />
<h2>Demo: pushed snackbar with custom CSS class</h2>
<br />
<p>
Pass one or more space-separated CSS class names via the <code>classNames</code> option to
apply custom styling to a snackbar that is pushed via the Nexus API.
</p>
<p>For example:</p>
<br />
<pre>
function pushSnackbarWithCustomClass() {
otto.components.snackbarV1.push("pushed-snackbar", { classNames: "demo-custom-snackbar" });
}
</pre>
<style>
.demo-custom-snackbar {
--background-color: var(--oc-semantic-color-background-strong-warm-red);
}
</style>
<oc-button-v1 id="push-custom-class" size="50">Push snackbar with custom class</oc-button-v1>
<script>
(() => {
const pushCustomClassButton = document.getElementById("push-custom-class");
pushCustomClassButton.addEventListener("click", pushSnackbarWithCustomClass);
function pushSnackbarWithCustomClass() {
otto.components.snackbarV1.push("pushed-snackbar", {
classNames: "demo-custom-snackbar",
});
}
})();
</script>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: pushed snackbar with custom CSS",
argTypes: hideControlsBadge(Metadata),
parameters: {
controls: {
disabled: true
},
chromatic: {
hideInChromatic: true
}
},
render() {
return html`
<br />
<h2>Demo: pushed snackbar with custom CSS class</h2>
<br />
<p>
Pass one or more space-separated CSS class names via the <code>classNames</code> option to
apply custom styling to a snackbar that is pushed via the Nexus API.
</p>
<p>For example:</p>
<br />
<pre>
function pushSnackbarWithCustomClass() {
otto.components.snackbarV1.push("pushed-snackbar", { classNames: "demo-custom-snackbar" });
}
</pre>
<style>
.demo-custom-snackbar {
--background-color: var(--oc-semantic-color-background-strong-warm-red);
}
</style>
<oc-button-v1 id="push-custom-class" size="50">Push snackbar with custom class</oc-button-v1>
<script>
(() => {
const pushCustomClassButton = document.getElementById("push-custom-class");
pushCustomClassButton.addEventListener("click", pushSnackbarWithCustomClass);
function pushSnackbarWithCustomClass() {
otto.components.snackbarV1.push("pushed-snackbar", {
classNames: "demo-custom-snackbar",
});
}
})();
</script>
`;
}
}
Demo: Action dismisses snackbar
Story: components-snackbar-variations--action-dismisses-snackbar · tags: components
Snackbar with action slot
Args: id=snackbar-with-action, message=Lorem ipsum dolor sit amet, consete sadipscing elitr., actionsSlot=<oc-link-v2 id="undo-link" variant="inverted" slot="actions" as-button><a>Undo</a></oc-link-v2>
<h2>Demo: Action dismisses snackbar</h2>
<oc-button-v1 id="open" size="50">Open snackbar</oc-button-v1>
<oc-snackbar-v1 id="snackbar-with-action" message="Lorem ipsum dolor sit amet, consete sadipscing elitr."> <oc-link-v2 id="undo-link" variant="inverted" slot="actions" as-button><a>Undo</a></oc-link-v2> </oc-snackbar-v1>
<script>
(() => {
const openButton = document.getElementById("open");
openButton.addEventListener("click", openSnackbar);
function openSnackbar() {
const snackbarElement = document.getElementById("snackbar-with-action");
snackbarElement.open = true;
}
const actionLink = document.getElementById("undo-link");
actionLink.addEventListener("click", () => {
document.getElementById("snackbar-with-action").open = false;
});
})();
</script>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: Action dismisses snackbar",
argTypes: hideControlsBadge(Metadata),
parameters: {
controls: {
disabled: true
}
},
args: {
id: "snackbar-with-action",
message: "Lorem ipsum dolor sit amet, consete sadipscing elitr.",
actionsSlot: `<oc-link-v2 id="undo-link" variant="inverted" slot="actions" as-button><a>Undo</a></oc-link-v2>`
},
render({
actionsSlot,
...props
}) {
return html`
<h2>Demo: Action dismisses snackbar</h2>
<oc-button-v1 id="open" size="50">Open snackbar</oc-button-v1>
<oc-snackbar-v1 ${spread(props)}> ${unsafeHTML(actionsSlot)} </oc-snackbar-v1>
<script>
(() => {
const openButton = document.getElementById("open");
openButton.addEventListener("click", openSnackbar);
function openSnackbar() {
const snackbarElement = document.getElementById("snackbar-with-action");
snackbarElement.open = true;
}
const actionLink = document.getElementById("undo-link");
actionLink.addEventListener("click", () => {
document.getElementById("snackbar-with-action").open = false;
});
})();
</script>
`;
}
}
Demo: snackbar returns focus on close
Story: components-snackbar-variations--snackbar-returns-focus · tags: components
Snackbar returns focus on close
Args: id=snackbar-returns-focus, message=Lorem ipsum dolor sit amet, consete sadipscing elitr., actionsSlot=<oc-link-v2 id="undo-link" variant="inverted" slot="actions">Undo</oc-link-v2>
<h2>Demo: Snackbar returns focus on close</h2>
<p>
Open a snackbar and observe how it returns the focus to the previous focused element, when
closed.
</p>
<div style="display: flex; gap: 10px;">
<oc-button-v1 id="open" size="50">Open snackbar</oc-button-v1>
<oc-button-v1 id="toggleAutoDismiss" size="50">Disable auto-dismiss</oc-button-v1>
</div>
<br />
<oc-button-v1>secondary button</oc-button-v1>
<br />
<oc-button-v1>another button</oc-button-v1>
<oc-snackbar-v1 id="snackbar-returns-focus" message="Lorem ipsum dolor sit amet, consete sadipscing elitr."> <oc-link-v2 id="undo-link" variant="inverted" slot="actions">Undo</oc-link-v2> </oc-snackbar-v1>
<br />
<oc-text-field-v1 placeholder="oh look a text field!">This is a text field</oc-text-field-v1>
<br />
<div style="width: 180px; height: 240px; display: grid;">
<oc-card-v2 as-button> If you didn't know: This is a card. </oc-card-v2>
</div>
<script>
(() => {
const openButton = document.getElementById("open");
const toggleAutoDismissButton = document.getElementById("toggleAutoDismiss");
openButton.addEventListener("click", openSnackbar);
toggleAutoDismissButton.addEventListener("click", () => {
const snackbarElement = document.getElementById("snackbar-returns-focus");
if (!snackbarElement) {
return;
}
snackbarElement.autoDismiss = !snackbarElement.autoDismiss;
toggleAutoDismissButton.textContent = snackbarElement.autoDismiss
? "Disable auto dismiss"
: "Enable auto dismiss";
});
function openSnackbar() {
const snackbarElement = document.getElementById("snackbar-returns-focus");
snackbarElement.open = true;
}
const actionLink = document.getElementById("undo-link");
actionLink.addEventListener("click", () => {
document.getElementById("snackbar-returns-focus").open = false;
});
})();
</script>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: snackbar returns focus on close",
argTypes: hideControlsBadge(Metadata),
parameters: {
controls: {
disabled: true
}
},
args: {
id: "snackbar-returns-focus",
message: "Lorem ipsum dolor sit amet, consete sadipscing elitr.",
actionsSlot: `<oc-link-v2 id="undo-link" variant="inverted" slot="actions">Undo</oc-link-v2>`
},
render({
actionsSlot,
...props
}) {
return html`
<h2>Demo: Snackbar returns focus on close</h2>
<p>
Open a snackbar and observe how it returns the focus to the previous focused element, when
closed.
</p>
<div style="display: flex; gap: 10px;">
<oc-button-v1 id="open" size="50">Open snackbar</oc-button-v1>
<oc-button-v1 id="toggleAutoDismiss" size="50">Disable auto-dismiss</oc-button-v1>
</div>
<br />
<oc-button-v1>secondary button</oc-button-v1>
<br />
<oc-button-v1>another button</oc-button-v1>
<oc-snackbar-v1 ${spread(props)}> ${unsafeHTML(actionsSlot)} </oc-snackbar-v1>
<br />
<oc-text-field-v1 placeholder="oh look a text field!">This is a text field</oc-text-field-v1>
<br />
<div style="width: 180px; height: 240px; display: grid;">
<oc-card-v2 as-button> If you didn't know: This is a card. </oc-card-v2>
</div>
<script>
(() => {
const openButton = document.getElementById("open");
const toggleAutoDismissButton = document.getElementById("toggleAutoDismiss");
openButton.addEventListener("click", openSnackbar);
toggleAutoDismissButton.addEventListener("click", () => {
const snackbarElement = document.getElementById("snackbar-returns-focus");
if (!snackbarElement) {
return;
}
snackbarElement.autoDismiss = !snackbarElement.autoDismiss;
toggleAutoDismissButton.textContent = snackbarElement.autoDismiss
? "Disable auto dismiss"
: "Enable auto dismiss";
});
function openSnackbar() {
const snackbarElement = document.getElementById("snackbar-returns-focus");
snackbarElement.open = true;
}
const actionLink = document.getElementById("undo-link");
actionLink.addEventListener("click", () => {
document.getElementById("snackbar-returns-focus").open = false;
});
})();
</script>
`;
}
}
Demo: Snackbar inside a sheet
Story: components-snackbar-variations--demo-snackbar-in-sheet · tags: components
<h2>Demo: Snackbar inside a sheet</h2>
<p>
Open any sheet variant and then open the snackbar inside it. The snackbar is automatically
positioned above the sheet's action bar — or above the sheet's bottom edge when there is no
action bar.
</p>
<div style="display: flex; flex-wrap: wrap; gap: 12px;">
${scenarios.map(({
triggerId,
label
}) => html`<oc-button-v1 id="${triggerId}" size="50">${label}</oc-button-v1>`)}
</div>
${scenarios.map(({
sheetId,
snackbarId,
snackbarTriggerId,
label,
alignment,
withActions
}) => html`
<oc-sheet-v1
id="${sheetId}"
headline="Demo sheet — ${label}"
adaptive-alignment="${alignment ?? "right"}"
>
<div style="padding: 20px;">
<p>Sheet variant: <strong>${label}</strong></p>
<br />
<oc-button-v1 id="${snackbarTriggerId}" size="50">
Open snackbar in this sheet
</oc-button-v1>
</div>
<oc-snackbar-v1 id="${snackbarId}" message="Snackbar inside sheet!">
<oc-link-v2 variant="inverted" slot="actions" as-button>Action</oc-link-v2>
</oc-snackbar-v1>
${withActions ? unsafeHTML(actionsSlot) : ""}
</oc-sheet-v1>
`)}
<script>
(() => {
const scenarios = ${unsafeHTML(JSON.stringify(scenarios))};
for (const { sheetId, snackbarId, triggerId, snackbarTriggerId } of scenarios) {
document.getElementById(triggerId).addEventListener("click", () => {
document.getElementById(sheetId).open = true;
});
document.getElementById(snackbarTriggerId).addEventListener("click", () => {
document.getElementById(snackbarId).open = true;
});
}
})();
</script>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: Snackbar inside a sheet",
argTypes: hideControlsBadge(Metadata),
parameters: {
controls: {
disabled: true
},
chromatic: {
hideInChromatic: true
},
itest: {
disableSnapshot: true
}
},
render() {
const variants: Array<{
id: string;
label: string;
alignment?: "left" | "center";
}> = [{
id: "right",
label: "Right / Bottom (default)"
}, {
id: "left",
label: "Left",
alignment: "left"
}, {
id: "center",
label: "Centered",
alignment: "center"
}];
const actionsSlot = `
<div slot="actions" style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
<oc-button-v1 onclick="closest('oc-sheet-v1').open=false" variant="secondary">Close</oc-button-v1>
<oc-button-v1 variant="primary">Label</oc-button-v1>
</div>
`;
const scenarios = variants.flatMap(({
id,
label,
alignment
}) => [true, false].map(withActions => ({
sheetId: `demo-sheet-${id}-${withActions ? "with-actions" : "no-actions"}`,
snackbarId: `snackbar-${id}-${withActions ? "with-actions" : "no-actions"}`,
triggerId: `open-${id}-${withActions ? "with-actions" : "no-actions"}`,
snackbarTriggerId: `open-snackbar-${id}-${withActions ? "with-actions" : "no-actions"}`,
label: `${label} ${withActions ? "(with actions)" : "(no actions)"}`,
alignment,
withActions
})));
return html`
<h2>Demo: Snackbar inside a sheet</h2>
<p>
Open any sheet variant and then open the snackbar inside it. The snackbar is automatically
positioned above the sheet's action bar — or above the sheet's bottom edge when there is no
action bar.
</p>
<div style="display: flex; flex-wrap: wrap; gap: 12px;">
${scenarios.map(({
triggerId,
label
}) => html`<oc-button-v1 id="${triggerId}" size="50">${label}</oc-button-v1>`)}
</div>
${scenarios.map(({
sheetId,
snackbarId,
snackbarTriggerId,
label,
alignment,
withActions
}) => html`
<oc-sheet-v1
id="${sheetId}"
headline="Demo sheet — ${label}"
adaptive-alignment="${alignment ?? "right"}"
>
<div style="padding: 20px;">
<p>Sheet variant: <strong>${label}</strong></p>
<br />
<oc-button-v1 id="${snackbarTriggerId}" size="50">
Open snackbar in this sheet
</oc-button-v1>
</div>
<oc-snackbar-v1 id="${snackbarId}" message="Snackbar inside sheet!">
<oc-link-v2 variant="inverted" slot="actions" as-button>Action</oc-link-v2>
</oc-snackbar-v1>
${withActions ? unsafeHTML(actionsSlot) : ""}
</oc-sheet-v1>
`)}
<script>
(() => {
const scenarios = ${unsafeHTML(JSON.stringify(scenarios))};
for (const { sheetId, snackbarId, triggerId, snackbarTriggerId } of scenarios) {
document.getElementById(triggerId).addEventListener("click", () => {
document.getElementById(sheetId).open = true;
});
document.getElementById(snackbarTriggerId).addEventListener("click", () => {
document.getElementById(snackbarId).open = true;
});
}
})();
</script>
`;
}
}
Demo: snackbar stack
Story: components-snackbar-variations--snackbar-stack · tags: components
Multiple snackbars stack automatically. Open any combination of them — each sits above the previous. Oldest at the top, newest at the bottom.
<h2>Demo: Snackbar stack</h2>
<p>
Click any button to open its snackbar. Open several at once to see them stack automatically.
No special setup needed — it works for any snackbar.
</p>
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<oc-button-v1 id="stack-btn-1" size="50">Item added to wishlist</oc-button-v1>
<oc-button-v1 id="stack-btn-2" size="50">Order placed</oc-button-v1>
<oc-button-v1 id="stack-btn-3" size="50">Changes saved</oc-button-v1>
</div>
<oc-snackbar-v1 id="stack-snackbar-1" message="Item added to wishlist."></oc-snackbar-v1>
<oc-snackbar-v1 id="stack-snackbar-2" message="Your order has been placed!"></oc-snackbar-v1>
<oc-snackbar-v1 id="stack-snackbar-3" message="Changes saved successfully."></oc-snackbar-v1>
<script>
(() => {
[1, 2, 3].forEach((n) => {
document.getElementById("stack-btn-" + n).addEventListener("click", () => {
document.getElementById("stack-snackbar-" + n).open = true;
});
});
})();
</script>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: snackbar stack",
argTypes: hideControlsBadge(Metadata),
parameters: {
controls: {
disabled: true
},
chromatic: {
hideInChromatic: true
}
},
render() {
return html`
<h2>Demo: Snackbar stack</h2>
<p>
Click any button to open its snackbar. Open several at once to see them stack automatically.
No special setup needed — it works for any snackbar.
</p>
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<oc-button-v1 id="stack-btn-1" size="50">Item added to wishlist</oc-button-v1>
<oc-button-v1 id="stack-btn-2" size="50">Order placed</oc-button-v1>
<oc-button-v1 id="stack-btn-3" size="50">Changes saved</oc-button-v1>
</div>
<oc-snackbar-v1 id="stack-snackbar-1" message="Item added to wishlist."></oc-snackbar-v1>
<oc-snackbar-v1 id="stack-snackbar-2" message="Your order has been placed!"></oc-snackbar-v1>
<oc-snackbar-v1 id="stack-snackbar-3" message="Changes saved successfully."></oc-snackbar-v1>
<script>
(() => {
[1, 2, 3].forEach((n) => {
document.getElementById("stack-btn-" + n).addEventListener("click", () => {
document.getElementById("stack-snackbar-" + n).open = true;
});
});
})();
</script>
`;
}
}
Demo: snackbar stack inside sheet
Story: components-snackbar-variations--snackbar-stack-in-sheet · tags: components
Snackbar stacking also works inside a sheet. Open the sheet, then trigger multiple snackbars to see them stack above the sheet's action bar.
<h2>Demo: Snackbar stack inside sheet</h2>
<p>Open the sheet, then click each button to trigger multiple stacked snackbars.</p>
<oc-button-v1 id="stack-sheet-open" size="50">Open sheet</oc-button-v1>
<oc-sheet-v1 id="stack-sheet" headline="Sheet with stacked snackbars">
<div style="padding: 20px; display: flex; flex-direction: column; gap: 12px;">
<oc-button-v1 id="stack-sheet-btn-1" size="50">Item added to wishlist</oc-button-v1>
<oc-button-v1 id="stack-sheet-btn-2" size="50">Order placed</oc-button-v1>
<oc-button-v1 id="stack-sheet-btn-3" size="50">Changes saved</oc-button-v1>
</div>
<oc-snackbar-v1
id="stack-sheet-snackbar-1"
message="Item added to wishlist."
></oc-snackbar-v1>
<oc-snackbar-v1
id="stack-sheet-snackbar-2"
message="Your order has been placed!"
></oc-snackbar-v1>
<oc-snackbar-v1
id="stack-sheet-snackbar-3"
message="Changes saved successfully."
></oc-snackbar-v1>
<div slot="actions" style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
<oc-button-v1
onclick="document.getElementById('stack-sheet').open=false"
variant="secondary"
>Close</oc-button-v1
>
<oc-button-v1 variant="primary">Confirm</oc-button-v1>
</div>
</oc-sheet-v1>
<script>
(() => {
document.getElementById("stack-sheet-open").addEventListener("click", () => {
document.getElementById("stack-sheet").open = true;
});
[1, 2, 3].forEach((n) => {
document.getElementById("stack-sheet-btn-" + n).addEventListener("click", () => {
document.getElementById("stack-sheet-snackbar-" + n).open = true;
});
});
})();
</script>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: snackbar stack inside sheet",
argTypes: hideControlsBadge(Metadata),
parameters: {
controls: {
disabled: true
}
},
render() {
return html`
<h2>Demo: Snackbar stack inside sheet</h2>
<p>Open the sheet, then click each button to trigger multiple stacked snackbars.</p>
<oc-button-v1 id="stack-sheet-open" size="50">Open sheet</oc-button-v1>
<oc-sheet-v1 id="stack-sheet" headline="Sheet with stacked snackbars">
<div style="padding: 20px; display: flex; flex-direction: column; gap: 12px;">
<oc-button-v1 id="stack-sheet-btn-1" size="50">Item added to wishlist</oc-button-v1>
<oc-button-v1 id="stack-sheet-btn-2" size="50">Order placed</oc-button-v1>
<oc-button-v1 id="stack-sheet-btn-3" size="50">Changes saved</oc-button-v1>
</div>
<oc-snackbar-v1
id="stack-sheet-snackbar-1"
message="Item added to wishlist."
></oc-snackbar-v1>
<oc-snackbar-v1
id="stack-sheet-snackbar-2"
message="Your order has been placed!"
></oc-snackbar-v1>
<oc-snackbar-v1
id="stack-sheet-snackbar-3"
message="Changes saved successfully."
></oc-snackbar-v1>
<div slot="actions" style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
<oc-button-v1
onclick="document.getElementById('stack-sheet').open=false"
variant="secondary"
>Close</oc-button-v1
>
<oc-button-v1 variant="primary">Confirm</oc-button-v1>
</div>
</oc-sheet-v1>
<script>
(() => {
document.getElementById("stack-sheet-open").addEventListener("click", () => {
document.getElementById("stack-sheet").open = true;
});
[1, 2, 3].forEach((n) => {
document.getElementById("stack-sheet-btn-" + n).addEventListener("click", () => {
document.getElementById("stack-sheet-snackbar-" + n).open = true;
});
});
})();
</script>
`;
}
}
Interaction tests (SnackbarV1.interactions.stories.ts)
Interaction test stories (automated tests, not usage patterns).
Close Snackbar On Close Button Click
Story: components-snackbar-interaction-tests--close-snackbar-on-close-button-click · tags: play-fn
Close Snackbar On ESC Key
Story: components-snackbar-interaction-tests--close-snackbar-on-esc-key · tags: play-fn