Banners

Banners

A prominent message that stays put until the user deals with it.

A banner carries an important message at the top of a view, in the flow of the page, with one or two actions on it. It stays until the user acts — nothing dismisses it on a timer, and nothing about it is scripted.

A <div class="banner"> is the container. A <p> is the message. Everything else is optional and in this order: a leading <span class="material-symbols">, a <div class="actions"> of buttons, and a trailing .icon-button that closes it. Removing the banner from the page is what "closing" means, and that is the page's job — there is no plugin to call.

<div class="banner">
  <span class="material-symbols" aria-hidden="true">cloud_off</span>
  <p>You're offline. Edits are saved locally and will sync later.</p>
  <div class="actions">
    <button class="button text" type="button">Retry</button>
  </div>
  <button class="icon-button" type="button" aria-label="Dismiss">
    <span class="material-symbols" aria-hidden="true">close</span>
  </button>
</div>

Banner, snackbar or dialog

Three components tell the user something, and the difference between them is how much of the user's attention they are entitled to take. Reach for the least of the three that does the job.

Component Lifetime Blocks the page Use it when
Snackbar 4–10 seconds, then gone No — it floats over the page Confirming something that already happened. Missing it costs nothing: "Photo saved", "Message sent".
Banner Until the user acts on it No — it sits in the flow and pushes content down A condition that persists and that the user can keep working around. Offline, a failed sync, an expiring trial, a cookie choice.
Dialog Until the user answers Yes — nothing else is reachable The user cannot continue without deciding, or the decision destroys something.

Two rules of thumb follow from that. If missing the message is harmless, it is a snackbar — a banner that reports a completed action is a banner that never earns its dismissal. And if the user can usefully carry on with the message on screen, it is not a dialog: blocking the page for something the user is allowed to ignore is how a product teaches people to dismiss its dialogs unread.

One banner at a time, at the top of the content it is about. Two stacked banners are two messages competing, and the second one loses.

Basic

The basic banner is a single row 56dp tall: icon, message, actions, close. The message grows the row when it wraps. Anything you leave out simply is not there — a message on its own is a valid banner.

<div class="banner">
  <p>Your trial ends in three days.</p>
  <div class="actions">
    <button class="button text" type="button">See plans</button>
  </div>
</div>

Below 600dp the actions take a line of their own under the message and the row grows to 112dp, because a phone-width row cannot hold a message and two buttons side by side without shrinking the message to nothing.

Standard and vibrant

Two colour variants. A standard banner sits on surface-container and is the default — quiet enough to live above content without competing with it. Add vibrant for primary-container, when the message is the most important thing on the screen. Both keep their close button in primary.

<div class="banner vibrant">
  <span class="material-symbols" aria-hidden="true">system_update</span>
  <p>A new version is ready to install.</p>
  <div class="actions">
    <button class="button text" type="button">Later</button>
    <button class="button text" type="button">Restart</button>
  </div>
  <button class="icon-button" type="button" aria-label="Dismiss update notice">
    <span class="material-symbols" aria-hidden="true">close</span>
  </button>
</div>

square flattens the 28dp corners to nothing, for a banner that runs flush under an app bar or against the edges of the window. It applies to the basic banner only: ExpressiveCSS gives the rich layout one shape and no square counterpart, so class="banner rich square" is a rich banner with rounded corners.

<div class="banner square">
  <span class="material-symbols" aria-hidden="true">wifi_off</span>
  <p>Connection lost. Reconnecting…</p>
</div>

Rich

rich gives the banner a heading, room for a longer message, and either a 24dp icon or an 80dp image beside it. The actions move to their own row underneath. Use it when the message needs a sentence rather than a clause — otherwise the basic row says the same thing in a third of the height.

<div class="banner rich vibrant">
  <span class="material-symbols" aria-hidden="true">shield_person</span>
  <h3>We updated our privacy policy</h3>
  <p>
    Analytics cookies are now off by default, and you can change
    what we collect at any time from your account settings.
  </p>
  <div class="actions">
    <button class="button text" type="button">Read the policy</button>
    <button class="button text" type="button">Got it</button>
  </div>
  <button class="icon-button" type="button" aria-label="Dismiss privacy notice">
    <span class="material-symbols" aria-hidden="true">close</span>
  </button>
</div>

An <img> takes the icon's place. It is drawn as an 80dp square with square corners, cropped to fit, and it needs an alt — descriptive when it says something the heading and the message do not, empty when it is decoration, as it is here.

<div class="banner rich">
  <img src="/photo-book.jpg" alt="">
  <h3>Your photo book is ready</h3>
  <p>
    Twenty-four pages, printed and bound. It ships within two
    business days once you approve the proof.
  </p>
  <div class="actions">
    <button class="button text" type="button">View proof</button>
  </div>
</div>

The rich layout is a named grid, so the five parts — leading element, heading, message, actions, close — can be written in whatever order reads best and each one collapses its track when it is absent. Pick the heading level that fits the page's outline; the banner styles whichever of h1h6 it finds.

Semantics

A banner is not role="banner". That role is the page header landmark: a page has one of those, and it may have several banners, so announcing each one as the site header buries the real header among them. The semantics suite blocks both spellings of that confusion — <header class="banner"> and role="banner" on the container.

The action row is .actions and never a <nav>. Retry, restart and dismiss are commands, not destinations, and a landmark for them would be one more identical entry in the landmark menu.

Icons are decoration: the message says what the leading icon says, and the close button carries its own aria-label, so both are aria-hidden="true". A banner that appears while the user is on the page — a connection dropping, a sync failing — should announce itself: put role="status" on it, which is polite and does not move focus. A banner that is there when the page loads has already been read in document order and needs nothing.

Tokens

Set these on the banner itself, or in a rule of your own that picks out the banners you want to change — .notice-area .banner { … }. They are declared on the component, so a value set on an ancestor element is shadowed by the banner's own default rather than inherited. The colours are shared by both variants; the geometry belongs to one layout or the other.

Token Default
--md-comp-banners-colorvar(--md-sys-color-surface-container)
--md-comp-banners-body-text-colorvar(--md-sys-color-on-surface)
--md-comp-banners-title-text-colorvar(--md-sys-color-on-surface)
--md-comp-banners-icon-colorvar(--md-sys-color-on-surface)
--md-comp-banners-close-button-colorvar(--md-sys-color-primary)
--md-comp-banners-basic-height56px (112px below 600dp)
--md-comp-banners-basic-shape28px (0 when square)
--md-comp-banners-basic-leading-space4px
--md-comp-banners-basic-trailing-space4px
--md-comp-banners-basic-top-space4px
--md-comp-banners-basic-bottom-space4px
--md-comp-banners-basic-icon-container-size48px
--md-comp-banners-basic-icon-size24px
--md-comp-banners-basic-body-text-top-space14px
--md-comp-banners-basic-body-text-bottom-space14px
--md-comp-banners-basic-no-icon-body-text-leading-space16px
--md-comp-banners-basic-actions-between-space8px
--md-comp-banners-basic-actions-leading-space8px
--md-comp-banners-basic-actions-trailing-space8px
--md-comp-banners-rich-shape28px
--md-comp-banners-rich-leading-space12px
--md-comp-banners-rich-trailing-space12px
--md-comp-banners-rich-top-space12px
--md-comp-banners-rich-bottom-space12px
--md-comp-banners-rich-icon-size24px
--md-comp-banners-rich-icon-text-space4px
--md-comp-banners-rich-image-size80px
--md-comp-banners-rich-image-text-space8px
--md-comp-banners-rich-leading-element-leading-space4px
--md-comp-banners-rich-with-image-leading-space20px
--md-comp-banners-rich-title-text-top-space14px
--md-comp-banners-rich-title-text-bottom-space14px
--md-comp-banners-rich-actions-between-space8px
--md-comp-banners-rich-actions-top-space12px

The message and the rich heading both carry the body-medium type role — the heading at weight 500, which is Material's emphasized body-medium. Action buttons are ordinary text buttons and take their colour from the theme, not from the banner.

  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.