OTTODesign System

Components

Snackbar

The snackbar is a temporary message that appears at the bottom of a page to provide feedback triggered by a user's action. I should not be used for critical information.

Configurator

LiveSnackbar: message, action, auto-dismiss and background. Press the button to show it at the bottom of the window.
Snackbar zeigen Rückgängig machen
HTML
<oc-button-v1 variant="secondary" fit-content data-live-open="#snackbar-cfg">Snackbar zeigen</oc-button-v1>
<oc-snackbar-v1 id="snackbar-cfg" message="Der Artikel wurde von deinem Merkzettel entfernt." auto-dismiss="false"><oc-link-v2 variant="inverted" as-button slot="actions">Rückgängig machen</oc-link-v2></oc-snackbar-v1>

Usage

Anatomy

Anatomy

Variants

The background color of the snackbar can be adjusted. Therefore you should use semantic design tokens with the prefix "$oc-semantic-color-background-strong-". If you want to use other colors please check with the design system team before doing so.

Press a button: the snackbar appears at the bottom of the window

Standard Grün (success) Rot (error)
Ansehen Ansehen Ansehen
Livecolored background
HTML
<oc-button-v1 variant="secondary" fit-content data-live-open="#snackbar-bg-default">Standard</oc-button-v1>
<oc-button-v1 variant="secondary" fit-content data-live-open="#snackbar-bg-success">Grün (success)</oc-button-v1>
<oc-button-v1 variant="secondary" fit-content data-live-open="#snackbar-bg-error">Rot (error)</oc-button-v1>
<oc-snackbar-v1 id="snackbar-bg-default" message="Deine Änderungen wurden gespeichert." auto-dismiss="false"><oc-link-v2 variant="inverted" as-button slot="actions">Ansehen</oc-link-v2></oc-snackbar-v1>
<oc-snackbar-v1 id="snackbar-bg-success" message="Deine Änderungen wurden gespeichert." auto-dismiss="false" style="--background-color: var(--oc-semantic-color-background-strong-success)"><oc-link-v2 variant="inverted" as-button slot="actions">Ansehen</oc-link-v2></oc-snackbar-v1>
<oc-snackbar-v1 id="snackbar-bg-error" message="Deine Änderungen wurden gespeichert." auto-dismiss="false" style="--background-color: var(--oc-semantic-color-background-strong-error)"><oc-link-v2 variant="inverted" as-button slot="actions">Ansehen</oc-link-v2></oc-snackbar-v1>

Behavior

States

The snackbar has no own states. It uses the states of link and icon button.

Open it, then hover, press and tab through the action and the close button

Snackbar zeigen
Rückgängig machen
LiveStates
HTML
<oc-button-v1 variant="secondary" fit-content data-live-open="#snackbar-states">Snackbar zeigen</oc-button-v1></div>
<oc-snackbar-v1 id="snackbar-states" message="Der Artikel wurde entfernt." auto-dismiss="false"><oc-link-v2 variant="inverted" as-button slot="actions">Rückgängig machen</oc-link-v2></oc-snackbar-v1>

Interaction

The snackbar is triggered by a user's action on other components. Once triggered, it appears at the bottom of the viewport and automatically disappears after five seconds. However, there is an auto-dismiss restriction:

The snackbar will not disappear as long as the mouse hovers over it, the focus is on it, or it is held by touch.

Make sure the snackbar does not disappear automatically when you place actions inside it to ensure users do not become stressed trying to access them. The snackbar can then be closed manually using the close button.

1. Click the button to trigger the snackbar

In den Warenkorb

2. Close it with the close button, click anywhere else, or wait five seconds

Liveinteraction
HTML
<oc-button-v1 variant="secondary" data-live-open="#snackbar-interaction">In den Warenkorb</oc-button-v1>
<oc-snackbar-v1 id="snackbar-interaction" message="Der Artikel liegt jetzt in deinem Warenkorb."></oc-snackbar-v1>

Animation

When the snackbar enters, it moves up and increases its opacity simultaneously. When the snackbar exits, it moves down outside the viewport and decreases its opacity simultaneously. In the case of "prefers-reduced-motion", only the opacity animates.

Enter: moves up and fades in

Snackbar einblenden
Ansehen
LiveEnter animation
HTML
<oc-button-v1 variant="secondary" fit-content data-live-open="#snackbar-enter">Snackbar einblenden</oc-button-v1></div>
<oc-snackbar-v1 id="snackbar-enter" message="Deine Adresse wurde gespeichert." auto-dismiss="false"><oc-link-v2 variant="inverted" as-button slot="actions">Ansehen</oc-link-v2></oc-snackbar-v1>

Exit: moves down and fades out

EinblendenAusblenden
Ansehen
LiveExit animation
HTML
<oc-button-v1 variant="secondary" fit-content data-live-open="#snackbar-exit">Einblenden</oc-button-v1><oc-button-v1 variant="secondary" fit-content data-live-close="#snackbar-exit">Ausblenden</oc-button-v1></div>
<oc-snackbar-v1 id="snackbar-exit" message="Deine Adresse wurde gespeichert." auto-dismiss="false"><oc-link-v2 variant="inverted" as-button slot="actions">Ansehen</oc-link-v2></oc-snackbar-v1>

Fitting

For breakpoint S the snackbar uses fill-parent with 24px outer spacing on each side. As soon as there is not enough space for the action on the right side it is positioned below the label.

Mobile: width fills the window minus 24 px each side, height fits the content (narrow the window)

Kurzes LabelLanges Label
Action Action
LiveSnackbar with on line and multiple lines on mobile
HTML
<oc-button-v1 variant="secondary" fit-content data-live-open="#snackbar-fit-m-short">Kurzes Label</oc-button-v1><oc-button-v1 variant="secondary" fit-content data-live-open="#snackbar-fit-m-long">Langes Label</oc-button-v1></div>
<oc-snackbar-v1 id="snackbar-fit-m-short" message="Label" auto-dismiss="false"><oc-link-v2 variant="inverted" as-button slot="actions">Action</oc-link-v2></oc-snackbar-v1>
<oc-snackbar-v1 id="snackbar-fit-m-long" message="Ein außergewöhnlich langes Label" auto-dismiss="false"><oc-link-v2 variant="inverted" as-button slot="actions">Action</oc-link-v2></oc-snackbar-v1>

For breakpoint M to XL the snackbar uses fit-content. As soon as there is not enough space inside the viewport it switches to fill-parent with 24px outer spacing on each side.

Desktop: fit-content up to the window width minus 24 px each side; long labels move the action below

KurzLangSehr lang
Action Action Action
LiveSnackbar with on line and multiple lines on desktop
HTML
<oc-button-v1 variant="secondary" fit-content data-live-open="#snackbar-fit-d-short">Kurz</oc-button-v1><oc-button-v1 variant="secondary" fit-content data-live-open="#snackbar-fit-d-long">Lang</oc-button-v1><oc-button-v1 variant="secondary" fit-content data-live-open="#snackbar-fit-d-longer">Sehr lang</oc-button-v1></div>
<oc-snackbar-v1 id="snackbar-fit-d-short" message="Label" auto-dismiss="false"><oc-link-v2 variant="inverted" as-button slot="actions">Action</oc-link-v2></oc-snackbar-v1>
<oc-snackbar-v1 id="snackbar-fit-d-long" message="Ein wirklich extrem außergewöhnlich langes Label, das super viel Platz weg nimmt." auto-dismiss="false"><oc-link-v2 variant="inverted" as-button slot="actions">Action</oc-link-v2></oc-snackbar-v1>
<oc-snackbar-v1 id="snackbar-fit-d-longer" message="Ein wirklich extrem außergewöhnlich langes Label, das super mega viel Platz weg nimmt." auto-dismiss="false"><oc-link-v2 variant="inverted" as-button slot="actions">Action</oc-link-v2></oc-snackbar-v1>

Placement

The snackbar appears centered at the bottom of the viewport. It is positioned 16px from the bottom and 24px from the left and right sides. The snackbar is placed sticky (level 3) on top.

Centred at the bottom of the window: 16 px from the bottom, at least 24 px from the sides, elevation level 3

Snackbar zeigen
Ansehen
Livemobile placement of the snackbar
HTML
<oc-button-v1 variant="secondary" fit-content data-live-open="#snackbar-placement">Snackbar zeigen</oc-button-v1></div>
<oc-snackbar-v1 id="snackbar-placement" message="Zum Merkzettel hinzugefügt." auto-dismiss="false"><oc-link-v2 variant="inverted" as-button slot="actions">Ansehen</oc-link-v2></oc-snackbar-v1>

Best practices

Snackbar zeigen
Ansehen
DoShow only one snackbar at a time. Replace the current snackbar when a new one appears.
Snackbar 1Snackbar 2
Action Action
Don'tAllow multiple snackbars to appear at once or stack snackbars above each other.

1. Aktion: remove the item, 2. Aktion: press “Rückgängig machen” in the snackbar

Artikel entfernen
Rückgängig machen
DoIf a "Rückgänig" or "Wiederholen" action is triggered another snackbar should follow to confirm the action.
CautionIf you already use a sticky component in your view be aware that the snackbar might overlap with it.

Content guidelines

DoKeep the label to one line, if possible.
Don'tUse stylized text or inline links.
DoUse a label that provides feedback on a user's action.
Don'tUse a label that has advertising purposes e.g. sale or discounts.
Don'tUse an action with the label "Verwerfen" or "Schließen", as the snackbar features it own close affordance.
Don'tUse a label that contains critical system messages.

Accessibility

For information on accessibility, refer to thetechnical documentation.

Status

Implementation

Live demo

Neu für dich

Sneaker aus Glattleder, Weiß
-20 %

OTTO products

Sneaker aus Glattleder, Weiß

99,99 € 79,99 €

Sessel „Ilvy“ aus Bouclé, Eiche natur
Neu

andas

Sessel „Ilvy“ aus Bouclé, Eiche natur

399,99 €

Over-Ear-Kopfhörer mit Noise Cancelling

Hanseatic

Over-Ear-Kopfhörer mit Noise Cancelling

59,99 €

Tap a heart: the snackbar confirms the action at the bottom of the window

Merkzettel ansehen
LiveReal OTTO components, rendered by the OTTO component runtime
HTML
<p class="demo-title" style="font-size:1.25rem">Neu für dich</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(9.5rem,1fr));gap:24px 16px"><article style="display:flex;flex-direction:column;gap:2px;min-width:0"><div style="position:relative;border-radius:16px;overflow:hidden;background:#f3f3f3"><img src="/previews/imagery/samples/otto-product-still/product-sneaker.webp" alt="Sneaker aus Glattleder, Weiß" width="600" height="600" loading="lazy" style="display:block;width:100%;height:auto;margin:0;mix-blend-mode:multiply"><div style="position:absolute;left:12px;bottom:12px;display:flex;gap:4px;flex-wrap:wrap"><oc-tag-v1 variant="sale" size="50">-20 %</oc-tag-v1></div><div style="position:absolute;right:8px;top:8px"><oc-icon-button-v3 variant="secondary" size="50" elevation-level="above" icon="wishlist" oc-aria-label="Auf den Merkzettel" data-live-open="#snackbar-demo"></oc-icon-button-v3></div></div><p class="demo-copy" style="margin-top:10px;color:var(--oc-semantic-color-text-secondary, #6d6d6d);text-transform:uppercase;font-size:0.75rem;letter-spacing:0.02em">OTTO products</p><p class="demo-copy">Sneaker aus Glattleder, Weiß</p><p class="demo-copy" style="margin-top:6px"><s style="color:var(--oc-semantic-color-text-secondary, #6d6d6d)">99,99 €</s> <strong style="color:var(--oc-semantic-color-text-sale, #e20020);font-size:1rem">79,99 €</strong></p></article><article style="display:flex;flex-direction:column;gap:2px;min-width:0"><div style="position:relative;border-radius:16px;overflow:hidden;background:#f3f3f3"><img src="/previews/imagery/samples/otto-product-still/product-armchair.webp" alt="Sessel „Ilvy“ aus Bouclé, Eiche natur" width="600" height="600" loading="lazy" style="display:block;width:100%;height:auto;margin:0;mix-blend-mode:multiply"><div style="position:absolute;left:12px;bottom:12px;display:flex;gap:4px;flex-wrap:wrap"><oc-tag-v1 variant="info-primary" size="50">Neu</oc-tag-v1></div><div style="position:absolute;right:8px;top:8px"><oc-icon-button-v3 variant="secondary" size="50" elevation-level="above" icon="wishlist" oc-aria-label="Auf den Merkzettel" data-live-open="#snackbar-demo"></oc-icon-button-v3></div></div><p class="demo-copy" style="margin-top:10px;color:var(--oc-semantic-color-text-secondary, #6d6d6d);text-transform:uppercase;font-size:0.75rem;letter-spacing:0.02em">andas</p><p class="demo-copy">Sessel „Ilvy“ aus Bouclé, Eiche natur</p><p class="demo-copy" style="margin-top:6px"><strong style="font-size:1rem">399,99 €</strong></p></article><article style="display:flex;flex-direction:column;gap:2px;min-width:0"><div style="position:relative;border-radius:16px;overflow:hidden;background:#f3f3f3"><img src="/previews/imagery/samples/otto-product-still/product-headphones.webp" alt="Over-Ear-Kopfhörer mit Noise Cancelling" width="600" height="600" loading="lazy" style="display:block;width:100%;height:auto;margin:0;mix-blend-mode:multiply"><div style="position:absolute;right:8px;top:8px"><oc-icon-button-v3 variant="secondary" size="50" elevation-level="above" icon="wishlist" oc-aria-label="Auf den Merkzettel" data-live-open="#snackbar-demo"></oc-icon-button-v3></div></div><p class="demo-copy" style="margin-top:10px;color:var(--oc-semantic-color-text-secondary, #6d6d6d);text-transform:uppercase;font-size:0.75rem;letter-spacing:0.02em">Hanseatic</p><p class="demo-copy">Over-Ear-Kopfhörer mit Noise Cancelling</p><p class="demo-copy" style="margin-top:6px"><strong style="font-size:1rem">59,99 €</strong></p></article></div>
<oc-snackbar-v1 id="snackbar-demo" message="Der Artikel liegt jetzt auf deinem Merkzettel." auto-dismiss="false"><oc-link-v2 variant="inverted" as-button slot="actions">Merkzettel ansehen</oc-link-v2></oc-snackbar-v1>

Code

Version Tag Status API
v1 <oc-snackbar-v1> Stable, allowed for generation SnackbarV1

Overview (v1)

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
HTML
<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)

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.

HTML
<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

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)

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

HTML
<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