Design
Implementation

Snackbar - Design

Snackbars are quick popups that display a message to a user.


 

Live Demo

 

Usage

Snackbars are a simple popup that can perform some kind of action. On the spencers website, snackbars can perform an action, be dismissed by swiping, and handle user input. It is useful for showing users a warning/info message that needs attention. Snackbars can be used in the areas where a simple popup message needs to be displayed along with an option to perform an action. For Example: In the GMail application, when you delete Mail, a quick Snackbar displays at the bottom with Message '1 Deleted' with an action button 'Undo'. On pressing the 'Undo' action button, the deleted mail will be restored.

 

Anatomy

1. Container
2. Text
3. Dismiss Icon

 

Behavior

The snackbar container sits just off the screen at the bottom left, and when prompted it transforms to be just on the screen. The container for the message scales vertically depending on the size of the message. When the dismiss button is clicked, the snackbar transitions back into its place just off the screen.

Snackbar - Implementation

Snackbars are quick popups that display a message to a user.


 

Basic Snackbar

<div role="dialog" id="snackbar-1" class="snackbar" tabindex="-1" aria-modal="true" aria-label=" An warning/error has occured.">
    <div class="snackbar__label" role="status" aria-live="polite">
    This website uses cookies and other tracking technologies as described in our <a href="#" class="link">privacy policy</a>. California residents please see our <a href="#" class="link">CCPA Notice</a>.
    </div>
    <div class="snackbar__actions">
        <button class="btn snackbar__dismiss" aria-label="Dismiss">
            <svg class="icon" role="presentation" focusable="false" viewBox="0 0 24 24">
                <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/>
            </svg>
        </button>
    </div>
</div>

<script>
class SnackbarManager {
  constructor(snacks) {
    if (!Array.isArray(snacks)) throw Error('Snackbar Manager requires an array to initiate.');
    this.snacks = snacks;
  }


}

class Snackbar {
  constructor(opts) {
    if (typeof opts !== 'object') {
      throw Error("Snackbar requires an object to be passed to iniate")
    }

    this.$el = opts.el;
    this.$disclosure = document.querySelector("#snackbar-disclosure-" + this.$el.id.substr(this.$el.id.length - 1));
    this.$dismiss = this.$el.querySelector('.snackbar__dismiss');

    this.timer = null;
    this.timeout = opts.timeout || 5000;

    this.open = this.open.bind(this);
    this.close = this.close.bind(this);
    this.handleTransitionEnd = this.handleTransitionEnd.bind(this);

    this.$disclosure && this.$disclosure.addEventListener('click', this.open, false);
    this.$dismiss && this.$dismiss.addEventListener('click', this.close, false);
    this.$el.addEventListener('transitionend', this.handleTransitionEnd, false);
  }

  handleTransitionEnd() {
    window.setTimeout(() => {
      if (this.timer) {
        window.clearTimeout(this.timer);
        this.timer = null;
      }

      this.close();
    }, this.timeout);
  }

  open() {
    this.$disclosure && this.$disclosure.setAttribute('aria-expanded', true);
    this.$el.classList.add('snackbar--open');
  }

  close() {
    window.clearTimeout(this.timer);
    this.timer = null;
    this.$disclosure && this.$disclosure.setAttribute('aria-expanded', false);
    this.$el.classList.remove('snackbar--open');
  }
}

const snacks = Array.from(document.querySelectorAll('.snackbar'))
  .map(snack => new Snackbar({ el: snack, timeout: 1000000 }));

new SnackbarManager(snacks);
</script>
.snackbar {
    position: fixed;
    display: grid;
    grid-template-columns: 1fr 36px;
    grid-gap: 8px;
    will-change: transform;
    transition: transform .3s ease-in-out;
    align-items: center;
    justify-content: flex-start;
    box-sizing: border-box;
    pointer-events: none;
    -webkit-tap-highlight-color: rgba(0,0,0,0);
    box-shadow: 0 3px 5px -1px rgba(0,0,0,.2), 0 6px 10px 0 rgba(0,0,0,.14), 0 1px 18px 0 rgba(0,0,0,.12);
    border-radius: 4px 4px 0 0;
    left: 0;
    bottom: 0;
    min-width: 320px;
    width: 100%;
    max-width: 672px;
    padding: 16px 8px;
    padding-right: 0;
    background-color: #333;
    color: #fff;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
    transform: scale(.8);
    opacity: 0;
    transition: opacity .3s cubic-bezier(0,0,.2,1), transform .3s cubic-bezier(0,0,.2,1);
}

.snackbar__actions {
    display: grid;
    grid-gap: 8px;
    align-items: center;
    align-self: flex-start;
}

.snackbar__dismiss {
    align-self: flex-start;
    min-width: 36px;
    width: 36px;
    height: 36px;
    padding: 0;
    border: 2px dotted transparent;
}

.snackbar__dismiss:focus,
.snackbar__dismiss:hover {
    background-color: transparent;
}

.snackbar__dismiss:hover {
    border-color: #fff;
}

.snackbar__dismiss .icon {
    fill: #fff;
}

.snackbar.snackbar--open {
    transform: scale(1);
    opacity: 1;
    pointer-events: auto;
}

@media (min-width: 560px) {
    .snackbar {
        left: 8px;
        bottom: 8px;
        border-radius: 4px;
        padding: 16px;
    }
}