# Snackbar (w-snackbar)

## Description

A Snackbar shows brief user feedback messages that overlay content, with an optional action such as Undo.

Include one `<w-snackbar></w-snackbar>` in the document `<body>`. Use the `create` function to create messages.

[Warp component reference](https://warp-ds.github.io/docs/components/snackbar/frameworks/elements)

## Usage

Add `<w-snackbar>` as a direct child of `<body>`.

If you use a shared HTML template this may already have been done for you. You can check to see by running a query in your browser console:

```js
 // this should find an element,
 // if not you should add <w-snackbar></w-snackbar> to <body>
document.querySelector("w-snackbar");
```

`<w-snackbar>` sets up an ARIA live region that's ready for snackbar messages. We set this up ahead of time since some browsers and screen reader combinations have known issues when it comes to adding a live region and content at the same time.

```html
<body>
    <w-snackbar></w-snackbar>
</body>
```

### `<w-snackbar>` JS API

The `<w-snackbar>` element includes a [create function](#create) which you can use to create snackbar items.

```js
const snackbar = document.querySelector("w-snackbar");
const snackbarItem = snackbar.create("Snackbar message");
```

### Snackbar item lifecycle

A snackbar item animates in from and out of the bottom of the screen unless the user has indicated a preference for reduced motion, in which case it appears and disappears without animation.

If more than one snackbar item is active at a time they will stack, with the new messages animating in above existing ones with the topmost item as the starting point of the animation.

However, only one snackbar item should be visible at a time. Be mindful of what triggers snackbars in your application to avoid creating a wall of snackbar items, for example in bulk editing operations.

#### Duration

Snackbar items automatically close after either:

- 4 seconds (short) 
- 10 seconds (long)

You can also have a snackbar item last indefinitely, forcing users to dismiss it. We strongly encourage you to reconsider whether a snackbar is the right choice for you if you feel you need an indefinite snackbar item though, for the accessibility reasons outlined above.

##### Pausing the countdown

To give people a better chance to read the snackbar message and consider taking action the duration countdown timer pauses when:

- A pointer hovers over the snackbar item.
- Either the action or close buttons have keyboard focus.

#### Closing

Users can close a snackbar item ahead of its configured duration by:

- Pressing the close button (present by default).
- Pressing the action button (if present).
- Pressing <kbd>Escape</kbd> on a keyboard.

## Accessibility

From an accessibility perspective the snackbar component is problematic.

- Since the snackbar items disappear on their own users might not even see them, especially when zoomed in.
- The snackbar items are in practice impossible to reach with keyboard since they appear in the DOM away from the element that triggered them.
- Important elements on the page might get hidden behind the snackbar item.

A snackbar must never be the backbone of your user experience, it should only ever include information and actions that are available elsewhere.

- Keep messages and actions short since the snackbar item is available only for a short time.
- Ensure any information and action in the snackbar, such as Undo, is possible to do elsewhere.

`<w-snackbar>` creates an ARIA live region so messages you add to the snackbar get announced to assistive technologies.

### Keyboard behavior

`<w-snackbar>` is problematic from a keyboard user perspective. We do not override the default <kbd>Tab</kbd> behavior, with the result that the focusable buttons in a snackbar item probably come first or last in the focus order, depending on what end of `<body>` you put `<w-snackbar>`. We're investigating ways to provide keyboard shortcuts for moving focus to the snackbar item, but have yet to conclude on a design.

`<w-snackbar>` sets up a keyboard event listener on `document`. Pressing <kbd>Escape</kbd> while a snackbar item is open will close it. If more than one item is open the oldest one will close.

## Examples

### Default

Snackbar uses the `neutral` variant with a short duration by default.

<elements-example>

```html
<w-button data-testid="neutral-noaction">Neutral variant</w-button>

<script>
document.querySelector('w-button[data-testid="neutral-noaction"]').onclick = () => {
    const snackbar = document.querySelector("w-snackbar");
    snackbar.create("Snackbar message");
};
</script>
```

</elements-example>

#### With action

You can include an action in the snackbar by providing a label and an onclick handler. By default the action button and close button are inline with the text.

Clicking an action button automatically closes the snackbar item. If you want to prevent this call `event.preventDefault()`.

You can use an action with any `variant`.

<elements-example>

```html
<w-button data-testid="neutral-action">Neutral variant with action</w-button>

<script>
document.querySelector('w-button[data-testid="neutral-action"]').onclick = () => {
    const snackbar = document.querySelector("w-snackbar");
    snackbar.create("Snackbar message", {
        action: {
            label: "Undo",
            onclick(e) {
                console.log(e);
            }
        }
    });
};
</script>
```

</elements-example>

#### With long action label

By default a long label will result in the action and close buttons on a separate row. A label gets treated as long if it contains whitespace.

Clicking an action button automatically closes the snackbar item. If you want to prevent this call `event.preventDefault()`.

You can use an action with any `variant`.

<elements-example>

```html
<w-button data-testid="neutral-longlabel">Neutral variant, long label</w-button>

<script>
document.querySelector('w-button[data-testid="neutral-longlabel"]').onclick = () => {
    const snackbar = document.querySelector("w-snackbar");
    snackbar.create("Snackbar message", {
        action: {
            label: "Undo this thing",
            onclick(e) {
                console.log(e);
            }
        }
    });
};
</script>
```

</elements-example>

### Positive

<elements-example>

```html
<w-button data-testid="positive-noaction">Positive variant</w-button>

<script>
document.querySelector('w-button[data-testid="positive-noaction"]').onclick = () => {
    const snackbar = document.querySelector("w-snackbar");
    snackbar.create("Snackbar message", {
        variant: "positive",
    });
};
</script>
```

</elements-example>

### Warning

<elements-example>

```html
<w-button data-testid="warning-noaction">Warning variant</w-button>

<script>
document.querySelector('w-button[data-testid="warning-noaction"]').onclick = () => {
    const snackbar = document.querySelector("w-snackbar");
    snackbar.create("Snackbar message", {
        variant: "warning",
    });
};
</script>
```

</elements-example>

### Negative

<elements-example>

```html
<w-button data-testid="negative-noaction">Negative variant</w-button>

<script>
document.querySelector('w-button[data-testid="negative-noaction"]').onclick = () => {
    const snackbar = document.querySelector("w-snackbar");
    snackbar.create("Snackbar message", {
        variant: "negative",
    });
};
</script>
```

</elements-example>

### Info

<elements-example>

```html
<w-button data-testid="info-noaction">Info variant</w-button>

<script>
document.querySelector('w-button[data-testid="info-noaction"]').onclick = () => {
    const snackbar = document.querySelector("w-snackbar");
    snackbar.create("Snackbar message", {
        variant: "info",
    });
};
</script>
```

</elements-example>

### Neutral with custom icon

If you have cases not covered by the `create` API you can change the item returned by `create`.

For example, here is how you can add a custom icon to the neutral variant.

```ts
const snackbar = document.querySelector<WarpSnackbar>('w-snackbar');

// the default neutral variant is without an icon
const snackbarItem = snackbar!.create('The message should be short'); 

const customIcon = document.createElement('w-icon');
customIcon.name = 'Rocket';
customIcon.slot = 'icon';
snackbarItem.prepend(customIcon);
```

### Create `<w-snackbar-item>` manually

Alternatively you can create a `w-snackbar-item` manually and call [prepend](https://developer.mozilla.org/en-US/docs/Web/API/Element/prepend) on `<w-snackbar>`. The snackbar item handles its entire lifecycle on its own.

Note that by not using `snackbar.create()` you have to create and wire up event handlers any close- and action button.

```ts
const snackbar = document.querySelector("w-snackbar");
const item = document.createElement("w-snackbar-item");
item.variant = "info";
item.innerHTML = /* html */ `
    <w-icon slot="icon" name="Rocket"></w-icon>
    Warp speed engaged!
`;
snackbar.prepend(item);
```

## Styling

Snackbar supports styling through **component tokens** (CSS custom properties with a --w-c- prefix) and **parts**.

### Parts

Use `::part(...)` from outside the component.

#### `<w-snackbar>` parts

- `container` - the live region that positions messages on the screen.

#### `<w-snackbar-item>` parts

- `item` - the container wrapping the message, icon and actions.
- `icon` - the icon for the variant.
- `message` - the default slot, body of the snackbar item.
- `action` - close and action button.

### Component tokens

#### `<w-snackbar>` tokens

Set these on `w-snackbar` to override visuals.

```css
w-snackbar {
    --w-c-snackbar-position-bottom: 32px;
}
```

##### Layout and typography

- `--w-c-snackbar-container-gap`
- `--w-c-snackbar-position-bottom`
- `--w-c-snackbar-position-left`
- `--w-c-snackbar-position-right`
- `--w-c-snackbar-position-top`
- `--w-c-snackbar-z-index`

#### `<w-snackbar-item>` tokens

Set these on `w-snackbar-item` to override visuals.

```css
w-snackbar-item {
    --w-c-snackbar-item-box-shadow: var(--w-shadow-s);
}
```

##### Layout and typography

- `--w-c-snackbar-item-radius`
- `--w-c-snackbar-item-max-width`
- `--w-c-snackbar-item-min-width`
- `--w-c-snackbar-item-padding`

##### Background

- `--w-c-snackbar-item-bg`
- `--w-c-snackbar-item-action-bg-active`
- `--w-c-snackbar-item-action-bg-hover`

##### Text color

- `--w-c-snackbar-item-color`
- `--w-c-snackbar-item-action-color-active`
- `--w-c-snackbar-item-action-color-hover`

##### Shadow

- `--w-c-snackbar-item-box-shadow`

## `<w-snackbar>` API

Unless otherwise noted all properties are HTML attributes (as opposed to JavaScript object properties).

### Properties

| Name | Type | Default | Summary |
|-|-|-|-|
| create (JS only) | `create(message: string, options: CreateSnackbarOptions = {}) => void` | `-` | Creates a snackbar item and immediately adds it to the snackbar. |

### Property Details

#### create (JS only)

Creates a snackbar item and immediately adds it to the snackbar.

By default the snackbar item automatically closes after 4 seconds (`SnackbarDuration.Short`).

If you include an `action` in the options the default `duration` is
set to 10 seconds (`SnackbarDuration.Long`) and can not be made shorter.

A `duration` of 10 seconds or longer forces the close button to be visible.

The default `variant` is `neutral` which does not have an icon.

Set `duration` to `SnackbarDuration.Infinite` if you want a persistent message.

- Type: `create(message: string, options: CreateSnackbarOptions = {}) => void`
- Default: `-`

