# Banner

A one-line announcement across the top of a site or app: a short message, an optional link and an optional dismiss control, on a full-width strip.

Block: `smbk-banner`. Status: candidate. Figma: the Figma file, https://www.figma.com/community/file/1688324308358870630. Desktop and mobile: differ by presentation only.

## Options

| Axis | Values | Default | Meaning |
|---|---|---|---|
| `tone` | `default`, `info`, `success`, `warning`, `danger` | `default` | Neutral, or one of the four feedback families on their subtle surface with their border beneath, each with its own icon. |

Parts: `smbk-banner__content`, `smbk-banner__lead`, `smbk-banner__icon`, `smbk-banner__message`, `smbk-banner__link`, `smbk-banner__dismiss`

## Markup

```html
<div role="region" aria-label="Announcement" class="smbk-banner smbk-banner--tone-default">
  <div class="smbk-banner__content">
    <div class="smbk-banner__lead">
      <i
        class="ph ph-megaphone smbk-icon smbk-icon--size-md smbk-banner__icon"
        aria-hidden="true"
      ></i>
      <p class="smbk-banner__message">Get the Simbolik Design System for free. Figma and code, open source.</p>
    </div>
    <a
      class="smbk-link smbk-link--size-sm smbk-banner__link"
      href="#get"
    ><span class="smbk-link__label">Get the system</span></a>
  </div>
  <button
    type="button"
    class="smbk-btn smbk-btn--tone-ghost smbk-btn--size-sm smbk-btn--icon-only smbk-banner__dismiss"
    aria-label="Dismiss"
  >
    <i class="ph ph-x smbk-icon smbk-icon--size-md smbk-btn__icon" aria-hidden="true"></i>
  </button>
</div>
```

## React

```tsx
import { Banner } from './design-system/components/banner/banner.js';

<Banner tone="default" link={{ href: "#get", label: "Get the system" }} onDismiss={() => {}}>Get the Simbolik Design System for free. Figma and code, open source.</Banner>
```

## Stylesheet

```css
/* smbk-banner — Banner. Figma "Banner" 4381:2200. */

.smbk-banner {
  /* The neutral tone; each feedback tone sets these again below (the tone recipe). */
  --smbk-tone-surface: var(--smbk-color-surface-default);
  --smbk-tone-border: var(--smbk-color-border-subtle);
  --smbk-tone-text: var(--smbk-color-text-subtle);
  --smbk-tone-icon: var(--smbk-color-icon-subtle);
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: var(--smbk-space-inline-xl);
  width: 100%;
  min-height: var(--smbk-size-control-md);
  padding: var(--smbk-space-inset-xs) var(--smbk-space-inset-md);
  background: var(--smbk-tone-surface);
  /* The line along the bottom edge. Figma strokes it inside the frame, over the padding, so it takes
     no room: drawn as an inner shadow, as the accordion draws its own, the strip stays one control tall. */
  box-shadow: inset 0 calc(-1 * var(--smbk-border-width-hairline)) 0 var(--smbk-tone-border);
  color: var(--smbk-tone-text);
}

/* The lead (the icon and the message) and the link, centered in the room the dismiss control
   leaves. A message too long for one line wraps beside its icon, centered, and the link follows
   it or drops beneath it. Figma draws one line only in a wide space. */
.smbk-banner__content {
  display: flex;
  flex: 1 1 auto;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  column-gap: var(--smbk-space-inline-xl);
  row-gap: var(--smbk-space-stack-xs);
  min-width: 0;
  text-align: center;
}

/* The icon beside the message, its box as tall as the message's line so it sits on the first line's middle. */
.smbk-banner__lead {
  display: flex;
  align-items: flex-start;
  gap: var(--smbk-space-inline-md);
  min-width: 0;
}

.smbk-banner__icon {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  height: calc(var(--smbk-typography-body-sm-font-size) * var(--smbk-typography-body-sm-line-height));
  color: var(--smbk-tone-icon);
  font-size: var(--smbk-size-icon-md);
}

.smbk-banner__message {
  margin: 0;
  font-family: var(--smbk-typography-body-sm-font-family);
  font-size: var(--smbk-typography-body-sm-font-size);
  font-weight: var(--smbk-typography-body-sm-font-weight);
  line-height: var(--smbk-typography-body-sm-line-height);
  letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
}

.smbk-banner__dismiss {
  flex-shrink: 0;
}

/* ---- feedback tones: the tone recipe, each family's subtle surface, its border on the line beneath, its text and
   its icon ---- */

.smbk-banner--tone-info { --smbk-tone-surface: var(--smbk-color-feedback-info-surface-subtle); --smbk-tone-border: var(--smbk-color-feedback-info-border); --smbk-tone-text: var(--smbk-color-feedback-info-text-on-subtle); --smbk-tone-icon: var(--smbk-color-feedback-info-icon); }
.smbk-banner--tone-success { --smbk-tone-surface: var(--smbk-color-feedback-success-surface-subtle); --smbk-tone-border: var(--smbk-color-feedback-success-border); --smbk-tone-text: var(--smbk-color-feedback-success-text-on-subtle); --smbk-tone-icon: var(--smbk-color-feedback-success-icon); }
.smbk-banner--tone-warning { --smbk-tone-surface: var(--smbk-color-feedback-warning-surface-subtle); --smbk-tone-border: var(--smbk-color-feedback-warning-border); --smbk-tone-text: var(--smbk-color-feedback-warning-text-on-subtle); --smbk-tone-icon: var(--smbk-color-feedback-warning-icon); }
.smbk-banner--tone-danger { --smbk-tone-surface: var(--smbk-color-feedback-danger-surface-subtle); --smbk-tone-border: var(--smbk-color-feedback-danger-border); --smbk-tone-text: var(--smbk-color-feedback-danger-text-on-subtle); --smbk-tone-icon: var(--smbk-color-feedback-danger-icon); }

/* ---- the narrow form (Figma's Platform=Mobile): the strip grows to hold its words; the message
   wraps beside its icon and the link sits under them, all from the start edge. ---- */

@container style(--smbk-layout: narrow) {
  .smbk-banner__content {
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: flex-start;
    text-align: start;
  }
}

/* ---- touch: the link takes an invisible press area ----
   On a touch screen an invisible area, centred on it, takes the press out to the comfortable target in each direction
   it falls short of it; its look, its focus ring and its place stay as drawn. */

@media (pointer: coarse) {
  .smbk-banner__link {
    position: relative;
  }

  .smbk-banner__link::after {
    content: '';
    position: absolute;
    inset: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
  }
}
```

## Tokens it reads

```json
{
  "color": {
    "surface": {
      "default": {
        "$type": "color",
        "$value": "{{color.grey.100}}",
        "$description": "Standard surface: cards, panels, the header."
      }
    },
    "border": {
      "subtle": {
        "$type": "color",
        "$value": "{{color.grey.300}}",
        "$description": "Dividers and quiet edges."
      }
    },
    "text": {
      "subtle": {
        "$type": "color",
        "$value": "{{color.grey.700}}",
        "$description": "Secondary text: descriptions, metadata."
      }
    },
    "icon": {
      "subtle": {
        "$type": "color",
        "$value": "{{color.grey.700}}",
        "$description": "Quiet icons."
      }
    },
    "feedback": {
      "info": {
        "surface-subtle": {
          "$type": "color",
          "$value": "{{color.blue.200}}",
          "$description": "Info messages: pale fill."
        },
        "border": {
          "$type": "color",
          "$value": "{{color.blue.500}}",
          "$description": "Info messages: edge."
        },
        "text-on-subtle": {
          "$type": "color",
          "$value": "{{color.blue.700}}",
          "$description": "Info messages: text on the pale fill."
        },
        "icon": {
          "$type": "color",
          "$value": "{{color.blue.600}}",
          "$description": "The info tone’s own icon (an info sign), so the tone is never shown by colour alone."
        }
      },
      "success": {
        "surface-subtle": {
          "$type": "color",
          "$value": "{{color.green.100}}",
          "$description": "Success messages: pale fill."
        },
        "border": {
          "$type": "color",
          "$value": "{{color.green.500}}",
          "$description": "Success messages: edge."
        },
        "text-on-subtle": {
          "$type": "color",
          "$value": "{{color.green.700}}",
          "$description": "Success messages: text on the pale fill."
        },
        "icon": {
          "$type": "color",
          "$value": "{{color.green.600}}",
          "$description": "The success tone’s own icon (a check), so the tone is never shown by colour alone."
        }
      },
      "warning": {
        "surface-subtle": {
          "$type": "color",
          "$value": "{{color.orange.200}}",
          "$description": "Warning messages: pale fill."
        },
        "border": {
          "$type": "color",
          "$value": "{{color.orange.500}}",
          "$description": "Warning messages: edge."
        },
        "text-on-subtle": {
          "$type": "color",
          "$value": "{{color.orange.700}}",
          "$description": "Warning messages: text on the pale fill."
        },
        "icon": {
          "$type": "color",
          "$value": "{{color.orange.600}}",
          "$description": "The warning tone’s own icon (a warning sign), so the tone is never shown by colour alone."
        }
      },
      "danger": {
        "surface-subtle": {
          "$type": "color",
          "$value": "{{color.red.200}}",
          "$description": "Danger messages: pale fill."
        },
        "border": {
          "$type": "color",
          "$value": "{{color.red.500}}",
          "$description": "Danger messages: edge; the edge of a field in error (3:1)."
        },
        "text-on-subtle": {
          "$type": "color",
          "$value": "{{color.red.700}}",
          "$description": "Danger messages: text on the pale fill."
        },
        "icon": {
          "$type": "color",
          "$value": "{{color.red.600}}",
          "$description": "The danger tone’s own icon (an alert sign), so the tone is never shown by colour alone."
        }
      }
    }
  },
  "space": {
    "inline": {
      "xl": {
        "$type": "dimension",
        "$value": "{{space.16}}",
        "$description": "Gap between things side by side, extra large."
      },
      "md": {
        "$type": "dimension",
        "$value": "{{space.8}}",
        "$description": "Gap between things side by side, medium."
      }
    },
    "inset": {
      "xs": {
        "$type": "dimension",
        "$value": "{{space.4}}",
        "$description": "Padding inside a component, extra small."
      },
      "md": {
        "$type": "dimension",
        "$value": "{{space.8}}",
        "$description": "Padding inside a component, medium."
      }
    },
    "stack": {
      "xs": {
        "$type": "dimension",
        "$value": "{{space.4}}",
        "$description": "Gap between things stacked one above another, extra small."
      }
    }
  },
  "size": {
    "control": {
      "md": {
        "$type": "dimension",
        "$value": "{{size.40}}",
        "$description": "Height of a medium control, where controls insert by default: 40 comfortable, 32 compact. Dashboard recipe: use compact density; 40 for the page’s one main action and for forms; 32 for toolbars and filters, the size used most; 28 for actions inside table rows and cards. On phones switch to comfortable density, or give small controls an invisible 44 px tap area (size/target/comfortable)."
      }
    },
    "icon": {
      "md": {
        "$type": "dimension",
        "$value": "{{size.20}}",
        "$description": "Icon box, medium."
      }
    },
    "target": {
      "comfortable": {
        "$type": "dimension",
        "$value": "{{size.44}}",
        "$description": "Comfortable touch target (44)."
      }
    }
  },
  "border-width": {
    "hairline": {
      "$type": "dimension",
      "$value": "{{border-width.1}}",
      "$description": "Thin lines: dividers and almost every edge."
    }
  },
  "typography": {
    "body-sm": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "14px",
        "fontWeight": 400,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      }
    }
  }
}
```

## Purpose

A short announcement that runs across the full width at the very top of a site or an app, above the header or the app bar: a new release, planned maintenance, a trial about to end. It speaks about the whole site, not about the page under it, which is what separates it from an alert, which sits inside the page next to what it is about.

## Anatomy

- The block, a full-width strip with no rounding and one hairline line along its bottom edge, drawn inside the strip so it takes no room.
- The content part, which centers the lead and the link in the room the dismiss control leaves.
- The lead part, holding the icon part and the message part side by side.
- The icon part: a megaphone on the neutral tone, and on the feedback tones an info circle, a tick, a warning sign or a crossed circle, in the tone's icon color. Any Phosphor icon can replace it, and it can be left out.
- The message part, one short sentence.
- The link part, optional: the small link, underlined, placed after the message.
- The dismiss part, optional: the small ghost button with a cross.

## When to use

- News or a notice that concerns every page of a site, shown until the reader dismisses it or the situation ends.
- A notice that must stay, such as a service outage, without the dismiss control.

## When not to use

- A message about one page, form or section. Use an [alert](alert.md) in that place.
- A confirmation that an action went through. Use a [toast](toast.md).
- Anything that needs the reader to decide before going on. Use a [modal](modal.md).

## Behavior

None of its own. The dismiss control calls back and the page removes the banner; the page also remembers that it was dismissed, so it does not come back on the next page. The link goes wherever the page points it.

The strip is one line tall. In a wide space too narrow for the message, the message wraps beside its icon and stays centered, and the link follows it or drops beneath it; the strip grows to fit. In a narrow space, such as a phone, everything starts at the start edge: the message wraps beside its icon and the link sits under them. The form follows the layout switch, so an element around the banner can force either. The difference is presentational only.

## Accessibility

### What it does for you

- The strip is a region of the page with a name, "Announcement" unless the page gives it another, so a screen reader can find it and skip it.
- It is part of the page as it loads, so it is not announced as a live message.
- The dismiss control is a real button named "Dismiss".
- The tone is shown by the icon's shape and the words as well as the color.

### What you must do

- Give each banner its own name when a page shows more than one.
- Use an alert for a banner that appears while the reader is on the page, such as an outage notice pushed in later. That is an alert's job, not a banner's.
- When the reader dismisses the banner, move focus to the start of the page that remains, not to nowhere.
- Write the message so it makes sense without the color, and keep the icon on a feedback tone.
- Never give the strip the `banner` role. That role marks the page's own header, so a screen reader would report two headers. In plain markup, give it the region role and a name, as the wrapper does.
- Give the dismiss control's name, and every other word the banner says by itself, in the page's own language. They keep their English unless the page gives its own.

### How to test

- Tab through it: its link and the dismiss control take focus in turn, with the focus ring showing in both themes and in a forced-color mode.
- Press Enter, then Space on the dismiss control: each dismisses the banner, and focus lands on the page that remains.
- With a screen reader, the banner is found as a region named "Announcement" or the name you gave, and it is not announced when the page loads.
- With text at 200% and the page 320 pixels wide, the message wraps and the controls stay reachable.

### Keyboard

| Key | What it does |
|---|---|
| Tab | Moves focus to the banner's link, then to the dismiss control. |
| Enter | Follows the focused link, or presses the dismiss control. |
| Space | Presses the dismiss control. |

## Usage rules

### Do

- Keep the message to one short sentence and let the link carry the details.
- Show one banner at a time.
- Give the link words that say where it goes, such as "Read the notes", never "Click here".

### Don't

- Write a paragraph in it. It wraps into a block that pushes the page down.
- Stack banners, or use one for a message about the page below it.
- Pass a link component as the message. Give the wrapper the link as `link`, with its address and its words, and it draws the link after the message.
