OTTODesign System

Code

Snackbar

Storybook group: Components · Sidebar path: Components/Snackbar · Extracted 28.09.2026

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: () => void

    Closes the snackbar. Provides the same effect as setting the attribute open to false. This method provides the same effect as clicking the close button.

    Returns: void

    Example:

    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: "&lt;oc-link-v2 slot=&quot;actions&quot; variant=&quot;inverted&quot; &gt;Action&lt;/oc-link-v2&gt;" }); 
}
      </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: "&lt;oc-link-v2 slot=&quot;actions&quot; variant=&quot;inverted&quot; &gt;Action&lt;/oc-link-v2&gt;" }); 
}
      </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