Alert

A new version is available.

A new version is available.

Spruce CSS v3 brings design styles and a built-in dark mode. Read the upgrade guide.

Your changes have been saved.

Your changes have been saved.

Everyone on the team can see the new settings now.

Your trial ends in 3 days.

Your trial ends in 3 days.

Add a payment method to keep your projects. Choose a plan.

We couldn’t process your payment.

We couldn’t process your payment.

Your card was declined. Check the details or try another card.

@use 'sass:map';
@use 'sprucecss/scss/spruce' as *;

.alert {
  --alert-color: #{color('primary')};
  align-items: start;
  background-color: color-mix(in srgb, var(--alert-color) 8%, #{color('background')});
  border: config('border-width-base', $display) solid color-mix(in srgb, var(--alert-color) 30%, #{color('background')});
  border-inline-start-width: 0.25rem;
  border-radius: radius('md');
  color: color('text');
  display: flex;
  gap: spacer('s');
  line-height: config('line-height-md', $typography);
  padding: 0.9em 1.1em;

  + .alert {
    margin-block-start: spacer('s');
  }

  // One modifier per alert color (info, success, warning, danger).
  @each $name, $value in map.get($colors, 'alert') {
    &--#{$name} {
      --alert-color: #{color($name, 'alert')};
    }
  }

  &__icon {
    block-size: 1.25rem;
    color: var(--alert-color);
    flex-shrink: 0;
    inline-size: 1.25rem;
    margin-block-start: 0.1em;
  }

  &__text,
  &__title {
    color: color('heading');
    font-weight: 700;
    margin-block: 0;
  }

  &__caption {
    @include layout-stack('xxs');
    flex-grow: 1;

    a {
      color: inherit;
      font-weight: 600;
    }
  }

  &__close {
    @include clear-btn;
    @include transition($property: 'background-color');
    align-items: center;
    border-radius: radius('sm');
    color: color('heading');
    display: flex;
    flex-shrink: 0;
    justify-content: center;
    margin-block: -0.25em;
    margin-inline-start: auto;
    padding: 0.4em;

    &:hover {
      background-color: color-mix(in srgb, var(--alert-color) 15%, transparent);
    }

    &:focus-visible {
      outline: 2px solid color('focus-ring');
      outline-offset: 1px;
    }

    svg {
      block-size: 1rem;
      inline-size: 1rem;
    }
  }
}

.alert-region:not(:empty) {
  margin-block: spacer('m') spacer('s');
}
<div class="alert alert--info">
    <svg class="alert__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
    <p class="alert__text">A new version is available.</p>
</div>
<div class="alert alert--info" data-alert>
    <svg class="alert__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
    <div class="alert__caption">
        <p class="alert__title">A new version is available.</p>
        <p>Spruce CSS v3 brings design styles and a built-in dark mode. <a href="#">Read the upgrade guide</a>.</p>
    </div>
    <button class="alert__close" type="button" data-alert-close>
        <span class="sr-only">Dismiss</span>
        <svg aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M18 6 6 18M6 6l12 12"/></svg>
    </button>
</div>
<div class="alert alert--success">
    <svg class="alert__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>
    <p class="alert__text">Your changes have been saved.</p>
</div>
<div class="alert alert--success" data-alert>
    <svg class="alert__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>
    <div class="alert__caption">
        <p class="alert__title">Your changes have been saved.</p>
        <p>Everyone on the team can see the new settings now.</p>
    </div>
    <button class="alert__close" type="button" data-alert-close>
        <span class="sr-only">Dismiss</span>
        <svg aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M18 6 6 18M6 6l12 12"/></svg>
    </button>
</div>
<div class="alert alert--warning">
    <svg class="alert__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4M12 17h.01"/></svg>
    <p class="alert__text">Your trial ends in 3 days.</p>
</div>
<div class="alert alert--warning" data-alert>
    <svg class="alert__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4M12 17h.01"/></svg>
    <div class="alert__caption">
        <p class="alert__title">Your trial ends in 3 days.</p>
        <p>Add a payment method to keep your projects. <a href="#">Choose a plan</a>.</p>
    </div>
    <button class="alert__close" type="button" data-alert-close>
        <span class="sr-only">Dismiss</span>
        <svg aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M18 6 6 18M6 6l12 12"/></svg>
    </button>
</div>
<div class="alert alert--danger">
    <svg class="alert__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6M9 9l6 6"/></svg>
    <p class="alert__text">We couldn’t process your payment.</p>
</div>
<div class="alert alert--danger" data-alert>
    <svg class="alert__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6M9 9l6 6"/></svg>
    <div class="alert__caption">
        <p class="alert__title">We couldn’t process your payment.</p>
        <p>Your card was declined. Check the details or try another card.</p>
    </div>
    <button class="alert__close" type="button" data-alert-close>
        <span class="sr-only">Dismiss</span>
        <svg aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M18 6 6 18M6 6l12 12"/></svg>
    </button>
</div>
<!-- A message that appears after an action: announced by screen readers -->
<div class="alert-region" role="status" data-alert-region></div>
<button class="btn btn--primary btn--sm" type="button" data-alert-demo>Save changes</button>
(() => {
  // Dismissible alerts: move the focus to a sensible place before the alert disappears.
  document.addEventListener('click', (event) => {
    const close = event.target.closest('[data-alert-close]');

    if (!close) return;

    const alert = close.closest('[data-alert]');
    const next = alert.nextElementSibling?.querySelector('a, button') ?? alert.parentElement.querySelector('[data-alert-demo]');

    alert.remove();
    next?.focus();
  });

  // Insert a message into the live region, so screen readers announce it.
  const region = document.querySelector('[data-alert-region]');

  document.querySelector('[data-alert-demo]')?.addEventListener('click', () => {
    region.innerHTML = '<div class="alert alert--success"><p class="alert__text">Your changes have been saved.</p></div>';
  });
})();

Alert

Messages that inform about the state of the page or the result of an action: four types, a one-line and a detailed version, an icon and an optional dismiss button.

Variations

  • .alert--info, --success, --warning, --danger: one modifier for every color of the alert group in $colors, generated with a loop.
  • A single line (.alert__text) or a title with more text (.alert__caption, .alert__title).
  • Dismissible: add data-alert and a .alert__close button with data-alert-close.

Accessibility

  • Don't put role="alert" on messages that are in the HTML when the page loads: it's meant for urgent, dynamic messages, and screen readers would interrupt the user to read every alert at once. Static alerts are just content.
  • For a message that appears after an action, insert it into a live region that's already on the page: role="status" for non-urgent results ("Saved."), role="alert" for errors that need attention. The demo button shows this.
  • The type isn't communicated by color only: every type has its own icon, and the text should say what happened.
  • The dismiss button has a visually hidden "Dismiss" label; after dismissing, the focus moves to the next control instead of being lost.

Technical Details

  • The colors come from the alert tokens with color-mix(), so the background and the border follow the dark mode and every style (earlier versions computed fixed light colors in Sass).
  • Change the accent of any alert with the --alert-color custom property.
  • The thicker start border uses border-inline-start-width, so it moves to the right in RTL.

Documentation

Learn about Spruce CSS through our extensive documentation.

Components

Explore our UI library of accessible components built with Spruce CSS.

Blocks and Pages

Full-width page blocks and complete page templates, ready to copy.

Find us on GitHub

Did you find a bug? Have an idea or a question? Please open an issue to help us develop the project.