Notification Toasts ·

elloimbrandon

Notes from elloimbrandon

This design is based off of Google Material UI. I've always liked the simplicity and modern design of their components.

AI-assistedupdated 3d ago
Preview
@layer tokens, base, components;

@layer tokens {
  :root {
    color-scheme: light dark;

    --mui-success: #2e7d32;
    --mui-error: #d32f2f;
    --mui-warning: #ed6c02;
    --mui-info: #0288d1;

    --mui-font: "Roboto", "Helvetica", "Arial", sans-serif;
    --mui-radius: 4px;
    --mui-shadow-6:
      0 3px 5px -1px rgb(0 0 0 / 0.2),
      0 6px 10px 0 rgb(0 0 0 / 0.14),
      0 1px 18px 0 rgb(0 0 0 / 0.12);
    --mui-z-snackbar: 1400;

    --mui-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
    --mui-ease-out: cubic-bezier(0, 0, 0.2, 1);
    --mui-ease-in: cubic-bezier(0.4, 0, 1, 1);
    --mui-sharp: cubic-bezier(0.4, 0, 0.6, 1);

    --mui-shortest: 150ms;
    --mui-shorter: 200ms;
    --mui-short: 250ms;
    --mui-standard: 300ms;
    --mui-complex: 375ms;
    --mui-entering: 225ms;
    --mui-leaving: 195ms;
    --mui-ripple: 550ms;
    --mui-pulsate: 2500ms;

    --toast-gap: 0.5rem;
    --toast-offset: clamp(0.5rem, 3vw, 1.5rem);
    --toast-width: min(100vw - var(--toast-offset) * 2, 28rem);
    --toast-icon-size: 22px;
    --toast-icon-gap: 12px;

    --icon-success: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4C12.76,4 13.5,4.11 14.2,4.31L15.77,2.74C14.61,2.26 13.34,2 12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12M7.91,10.08L6.5,11.5L11,16L21,6L19.59,4.58L11,13.17L7.91,10.08Z'/></svg>");
    --icon-error: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15h2v2h-2zm0-8h2v6h-2zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z'/></svg>");
    --icon-warning: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 5.99L19.53 19H4.47L12 5.99M12 2L1 21h22L12 2zm1 14h-2v2h2v-2zm0-6h-2v4h2v-4z'/></svg>");
    --icon-info: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z'/></svg>");
  }

  @keyframes mui-ripple-enter {
    from { scale: 0; opacity: 0.1; }
    to   { scale: 1; opacity: 0.3; }
  }

  @keyframes mui-pulsate {
    0%, 100% { scale: 1; }
    50%      { scale: 0.92; }
  }
}

@layer base {
  .toasts :where(p) { margin: 0; }
  .toasts :where(button) { font: inherit; color: inherit; }
}

@layer components {
  .toasts {
    position: fixed;
    inset-block-end: var(--toast-offset);
    inset-inline-start: var(--toast-offset);
    z-index: var(--mui-z-snackbar);
    inline-size: var(--toast-width);
    display: flex;
    flex-direction: column;
    container: toasts / inline-size;
    interpolate-size: allow-keywords;
  }

  .toast {
    --tone: var(--mui-info);
    --icon: var(--icon-info);

    --toast-bg: light-dark(
      color-mix(in oklch, var(--tone) 10%, white),
      color-mix(in oklch, var(--tone) 16%, #121212)
    );
    --toast-fg: light-dark(
      color-mix(in oklch, var(--tone) 40%, black),
      color-mix(in oklch, var(--tone) 40%, white)
    );

    display: flex;
    align-items: flex-start;
    block-size: auto;
    padding: 6px 16px;
    border-radius: var(--mui-radius);
    background: var(--toast-bg);
    color: var(--toast-fg);
    box-shadow: var(--mui-shadow-6);
    font: 400 0.875rem / 1.43 var(--mui-font);
    letter-spacing: 0.01071em;

    & + & {
      margin-block-start: var(--toast-gap);
    }

    transition:
      opacity var(--mui-entering) var(--mui-ease-in-out),
      scale calc(var(--mui-entering) * 0.666) var(--mui-ease-in-out);

    @starting-style {
      opacity: 0;
      scale: 0.75 0.5625;
    }

    &[hidden] {
      display: none;
      opacity: 0;
      scale: 0.75 0.5625;
      block-size: 0;
      padding-block: 0;
      margin-block-start: 0;
      overflow: clip;

      transition:
        opacity var(--mui-leaving) var(--mui-ease-in-out),
        scale calc(var(--mui-leaving) * 0.666) var(--mui-ease-in-out) calc(var(--mui-leaving) * 0.333),
        block-size var(--mui-standard) var(--mui-ease-in-out) var(--mui-leaving),
        padding-block var(--mui-standard) var(--mui-ease-in-out) var(--mui-leaving),
        margin-block-start var(--mui-standard) var(--mui-ease-in-out) var(--mui-leaving),
        overflow 0s allow-discrete,
        display calc(var(--mui-leaving) + var(--mui-standard)) allow-discrete;
    }

    &::before {
      content: "";
      flex: none;
      inline-size: var(--toast-icon-size);
      block-size: var(--toast-icon-size);
      margin-block: 7px;
      margin-inline-end: var(--toast-icon-gap);
      background-color: var(--tone);
      mask: var(--icon) center / contain no-repeat;
      opacity: 0.9;
    }

    &[data-variant="success"] { --tone: var(--mui-success); --icon: var(--icon-success); }
    &[data-variant="error"]   { --tone: var(--mui-error);   --icon: var(--icon-error); }
    &[data-variant="warning"] { --tone: var(--mui-warning); --icon: var(--icon-warning); }
    &[data-variant="info"]    { --tone: var(--mui-info);    --icon: var(--icon-info); }
  }

  .toast-content {
    flex: 1 1 0;
    min-inline-size: 0;
    padding-block: 8px;
  }

  .toast-title {
    margin-block: -2px 0.35em;
    font-size: 1rem;
    font-weight: 500;
    line-height: 1.5;
    letter-spacing: 0.00938em;
  }

  .toast-body {
    text-wrap: pretty;
  }

  .toast-actions {
    flex: none;
    display: flex;
    align-items: center;
    gap: 4px;
    margin-inline: auto -8px;
    padding: 4px 0 0 16px;
  }

  :is(.toast-action, .toast-close) {
    --state: 0%;
    --ripple-size: 100%;
    position: relative;
    isolation: isolate;
    overflow: hidden;
    appearance: none;
    border: 0;
    outline: 0;
    cursor: pointer;
    background: color-mix(in oklch, currentColor var(--state), transparent);
    -webkit-tap-highlight-color: transparent;

    &:hover         { --state: 4%; }
    &:focus-visible { --state: 12%; }

    &::after {
      content: "";
      position: absolute;
      inset-block-start: 50%;
      inset-inline-start: 50%;
      z-index: -1;
      inline-size: var(--ripple-size);
      aspect-ratio: 1;
      translate: -50% -50%;
      border-radius: 50%;
      background: currentColor;
      pointer-events: none;
      opacity: 0;
      scale: 0;
      transition:
        opacity var(--mui-ripple) var(--mui-ease-in-out),
        scale 0s var(--mui-ripple);
    }

    &:active::after {
      opacity: 0.3;
      scale: 1;
      transition:
        opacity var(--mui-ripple) var(--mui-ease-in-out),
        scale var(--mui-ripple) var(--mui-ease-in-out);
    }

    &:focus-visible:not(:active)::after {
      opacity: 0.3;
      scale: 1;
      animation:
        mui-ripple-enter var(--mui-ripple) var(--mui-ease-in-out),
        mui-pulsate var(--mui-pulsate) var(--mui-ease-in-out) var(--mui-ripple) infinite;
    }
  }

  .toast-action {
    --ripple-size: 150%;
    padding: 4px 5px;
    min-inline-size: 64px;
    border-radius: var(--mui-radius);
    font-size: 0.8125rem;
    font-weight: 500;
    line-height: 1.75;
    letter-spacing: 0.02857em;
    text-transform: uppercase;
    transition:
      background-color var(--mui-short) var(--mui-ease-in-out),
      color var(--mui-short) var(--mui-ease-in-out);
  }

  .toast-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 30px;
    aspect-ratio: 1;
    padding: 0;
    border-radius: 50%;
    font-size: 1.375rem;
    line-height: 1;
    transition: background-color var(--mui-shortest) var(--mui-ease-in-out);
  }

  @container toasts (inline-size < 22rem) {
    .toast:has(.toast-action) {
      flex-wrap: wrap;
    }

    .toast:has(.toast-action) .toast-actions {
      flex-basis: 100%;
      justify-content: flex-end;
      padding: 0 0 4px calc(var(--toast-icon-size) + var(--toast-icon-gap));
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .toast,
    .toast[hidden],
    :is(.toast-action, .toast-close),
    :is(.toast-action, .toast-close)::after {
      transition-duration: 0s;
      transition-delay: 0s;
      animation: none;
    }
  }
}

Discussion 1

  1. jnevius3d ago

    Nice...I was wondering if someone would add in some icons. 👍

Sign in to join the discussion.