How to Design “Warning” and “Success” Banners

Alerts either calm users or save their day. By the end of this article you will build two production‑ready banners: a bold “Warning” banner that draws attention fast, and a calm “Success” banner that confirms an action without stealing focus. Both components use clean HTML, modern CSS, and small, CSS‑only icons that do not rely on external images.

Why Designing Warning and Success Banners Matters

Feedback UI is the safety net of any interface. A warning banner pulls the brakes when a user is at risk. A success banner closes the loop when a task completes. Good banners are clear, accessible, and fast to theme. When you express the iconography with CSS, you remove a network request, avoid mismatched asset scales, and gain full control over color and animation through custom properties.

Prerequisites

You will get the most out of this guide if you are comfortable with basic HTML and styling. Pseudo‑elements will draw our icons, and CSS variables will drive theming.

  • Basic HTML
  • CSS custom properties
  • CSS pseudo-elements (::before / ::after)

Step 1: The HTML Structure

The structure stays identical for both banners. Each banner has an icon slot, a text wrapper for title and message, and a close button. We will apply variants with modifier classes.

<div class="banner banner--warning" role="alert" aria-live="assertive">
  <div class="banner__icon" aria-hidden="true"></div>
  <div class="banner__content">
    <strong class="banner__title">Warning</strong>
    <p class="banner__message">Your session will expire in 2 minutes. Save your work.</p>
  </div>
  <button class="banner__close" aria-label="Dismiss">&times;</button>
</div>

<div class="banner banner--success" role="status" aria-live="polite">
  <div class="banner__icon" aria-hidden="true"></div>
  <div class="banner__content">
    <strong class="banner__title">Saved</strong>
    <p class="banner__message">Your changes are stored successfully.</p>
  </div>
  <button class="banner__close" aria-label="Dismiss">&times;</button>
</div>

The root .banner element sets the layout and the visual skeleton. The .banner–warning and .banner–success modifiers switch variables for color and icon styling. The icon container is empty in the markup because CSS will draw the shapes. The dismiss button is a plain button for keyboard and screen reader support.

Step 2: The Basic CSS & Styling

Start by declaring color tokens and layout variables. The base .banner class provides a grid layout, spacing, a subtle border, and a left accent rail. Variants will swap color variables and leave structure intact.

/* CSS */
:root {
  --space-1: 0.375rem;
  --space-2: 0.75rem;
  --space-3: 1rem;
  --radius: 10px;
  --icon-size: 28px;
  --border-w: 1px;

  /* Warning theme */
  --warning-bg: #fff8e6;
  --warning-ink: #5a3a00;
  --warning-accent: #ffb300;
  --warning-border: #ffd37a;

  /* Success theme */
  --success-bg: #eefbf3;
  --success-ink: #0f5132;
  --success-accent: #2ecc71;
  --success-border: #bfead1;

  /* Generic */
  --shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
  color-scheme: light dark;
}

*,
*::before,
*::after { box-sizing: border-box; }

body {
  margin: 0;
  font: 16px/1.5 system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
  background: linear-gradient(180deg, #f7f7f7, #f0f2f5);
  padding: 2rem;
  color: #222;
}

.banner {
  --bg: #fff;         /* default fallback */
  --ink: #111;
  --accent: #888;
  --border: #ddd;

  position: relative;
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: var(--space-2) var(--space-3);
  align-items: center;

  padding: var(--space-2) var(--space-3);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow);
  overflow: hidden;
}

.banner::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 6px;
  background: linear-gradient(#0000, #0002), var(--accent);
}

.banner__icon {
  width: var(--icon-size);
  height: var(--icon-size);
  position: relative;
  display: grid;
  place-items: center;
  margin-inline-start: 4px; /* visual centering against the rail */
}

.banner__content {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.banner__title {
  display: block;
  font-weight: 700;
  color: var(--ink);
}

.banner__message {
  margin: 0;
  color: color-mix(in oklab, var(--ink) 86%, black 0%);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.banner__close {
  appearance: none;
  border: 0;
  background: transparent;
  padding: var(--space-1);
  line-height: 1;
  border-radius: 6px;
  color: color-mix(in oklab, var(--ink) 75%, black 0%);
  cursor: pointer;
}

.banner__close:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

.banner__close:hover {
  background: color-mix(in srgb, var(--accent) 12%, white 88%);
}

Advanced Tip: Keep presentation in variables. With the base object set, variants only override –bg, –ink, –accent, and –border. This keeps your CSS small and theming trivial for design systems.

Step 3: Building the Warning Banner

The warning banner uses a yellow palette, a left accent rail, and a triangle caution icon. The triangle is drawn with borders, and the exclamation mark sits on top as text inside the same icon box.

/* CSS */
.banner--warning {
  --bg: var(--warning-bg);
  --ink: var(--warning-ink);
  --accent: var(--warning-accent);
  --border: var(--warning-border);
}

/* Triangle + "!" */
.banner--warning .banner__icon::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 54%;
  transform: translate(-50%, -50%);
  border-left: calc(var(--icon-size) * 0.5) solid transparent;
  border-right: calc(var(--icon-size) * 0.5) solid transparent;
  border-bottom: var(--icon-size) solid var(--warning-accent);
  filter: drop-shadow(0 1px 0 rgba(0,0,0,0.08));
}

.banner--warning .banner__icon::after {
  content: "!";
  position: relative;
  z-index: 1;
  font-weight: 900;
  font-size: calc(var(--icon-size) * 0.6);
  line-height: 1;
  color: #1f1400;
  transform: translateY(2px);
}

/* Optional callout notch on the right */
.banner--warning::after {
  content: "";
  position: absolute;
  right: -10px;
  top: 50%;
  transform: translateY(-50%);
  border-left: 10px solid var(--border);
  border-top: 10px solid transparent;
  border-bottom: 10px solid transparent;
}

How This Works (Code Breakdown)

The .banner–warning block swaps the theme variables, which updates background, text, border, and the accent rail from the base object. The rail itself is still drawn by .banner::before, so the variant only needs to define the –accent color value.

The triangle icon uses the classic border trick. The pseudo‑element sits at the center of the icon box and creates two transparent side borders and one solid bottom border, which forms a triangle that points up. If you want a primer on this method, see how to build an equilateral triangle with CSS. Since the icon box uses place‑items: center, the exclamation is easy to stack on top using ::after. The heavy weight improves legibility at small sizes.

The optional notch is a small triangle pointing right. It helps when the banner anchors to a UI element, like a form. That notch uses the same border trick with different sides set, similar to making a triangle right with CSS. The notch is attached to the banner box itself, not the icon.

Step 4: Building the Success Banner

The success banner switches to a green palette and a circle icon with a checkmark. A pure CSS circle is trivial with border‑radius, and the checkmark uses two borders rotated 45 degrees.

/* CSS */
.banner--success {
  --bg: var(--success-bg);
  --ink: var(--success-ink);
  --accent: var(--success-accent);
  --border: var(--success-border);
}

/* Circle + checkmark */
.banner--success .banner__icon {
  isolation: isolate; /* contain blending if you theme further */
}

.banner--success .banner__icon::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--success-accent);
  box-shadow: inset 0 -2px 0 rgba(0, 0, 0, 0.08);
}

.banner--success .banner__icon::after {
  content: "";
  position: absolute;
  width: 45%;
  height: 26%;
  border-right: 3px solid white;
  border-bottom: 3px solid white;
  transform: rotate(45deg);
  left: 28%;
  top: 32%;
}

How This Works (Code Breakdown)

The success variant again overrides variables to paint the base object. The icon’s ::before fills the full icon box and rounds to a perfect disc, a technique covered in how to make a circle with CSS. The subtle inset shadow gives a polished surface without flattening the color.

The checkmark is a common trick: a small rectangle rotated 45 degrees where only the right and bottom borders are visible. The width and height percentages keep the mark proportional as you scale –icon-size. Anchoring with left and top percentages centers the check within the circle while leaving enough margin around it for crisp readability.

Advanced Techniques: Animations and Interactions

Animation should guide, not distract. Use short entrance transitions and reserved micro‑motion for the icons. Respect users who prefer reduced motion and avoid jittery loops.

/* CSS */
@keyframes banner-slide-in {
  from { transform: translateY(-8px); opacity: 0; }
  to   { transform: translateY(0); opacity: 1; }
}

@keyframes warn-pulse {
  0%, 100% { filter: brightness(1); }
  50% { filter: brightness(1.1); }
}

.banner {
  animation: banner-slide-in 220ms ease-out both;
}

/* Subtle pulse on the warning triangle for 1 second, then stop */
.banner--warning .banner__icon::before {
  animation: warn-pulse 900ms ease-out 1;
}

/* Hover affordance on the close button */
.banner__close {
  transition: background-color 120ms ease, transform 120ms ease;
}
.banner__close:active { transform: scale(0.96); }

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
  .banner,
  .banner * {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
}

The banner-slide-in keyframes provide a small drop transition that makes the component feel responsive without stealing attention. The warn-pulse animation gives the triangle a quick brightness nudge on mount, then stops after one run to prevent distraction. The close button receives a small press feedback on active. The reduced‑motion media query exits animations for users who prefer a calmer interface.

Accessibility & Performance

Visuals mean little if the component leaves keyboard users and assistive tech behind. Keep states clear, respect user settings, and watch contrast.

Accessibility

Pick semantics based on urgency. The warning banner uses role=”alert” and aria-live=”assertive” which announces the message as soon as it appears. The success banner uses role=”status” and aria-live=”polite” because success feedback should not interrupt a user mid‑task. The icons are decorative here, so they carry aria-hidden=”true”. The button has an aria-label so the purpose is clear without reading the “×” symbol.

Color contrast should stay above WCAG AA. With themed systems, test the combination of –bg and –ink with tools or automated checks. The focus ring on the dismiss button uses outline so it remains visible on any background. For motion, the prefers-reduced-motion block keeps animation minimal for users who get motion sickness or prefer still interfaces.

Performance

These banners are cheap to render. The icons are pure CSS and require no network calls, and the layout uses a small grid with minimal paint. Short transforms and opacity on entrance are GPU friendly. Avoid heavy shadows and repeated infinite animations on long lists of banners, since those can lead to more paint work. Consider removing box-shadow on low‑end devices or long feeds if you measure any dropped frames.

Ship Banners That Get Read

You now have a flexible warning banner with a CSS triangle icon and a steady success banner with a circle checkmark. Both are theme‑ready, accessible, and easy to maintain. Expand the pattern with a neutral info banner, or wire the same structure to server‑driven messages and you will keep users informed without clutter.

Leave a Comment