component

Banner

A reusable, slot-rich title bar with flanking leading/trailing snippets and an optional nested heading + subheading, each with their own leading/trailing. Banner is built on top of Panel (surfaces, borders, padding, radius) so it can be used standalone as a section header, embedded inside a card or dialog, or dropped anywhere a “title with actions” pattern is needed.

Basic

A minimal banner with only a heading.

Project settings

---
import Banner from "../Banner.astro";
---

<Banner heading="Project settings" />

Heading + Subheading

Add a subheading for a two-line title stack.

Project settings

Manage workspace access, billing, and integrations

---
import Banner from "../Banner.astro";
---

<Banner
  heading="Project settings"
  subheading="Manage workspace access, billing, and integrations"
/>

Eyebrow

eyebrow adds a small kicker line above the heading — case-preserving and muted by default. It inherits panel-aware text color, so under emphasis="primary" it automatically flips to a contrast variant. Accepts the same string | { leading, content, trailing } shape as heading.

Whether the eyebrow reads as a quiet meta line, a label-style kicker, or an uppercase overline is a styling decision the consumer owns. Reach for passThrough.eyebrow.style (or write the text uppercased yourself) when you want a tighter, ALL-CAPS look:

<Banner
  eyebrow="Wednesday"
  heading="Dec 14"
  passThrough={{
    eyebrow: {
      style: { textTransform: "uppercase", letterSpacing: "0.05em" },
    },
  }}
/>

Wednesday

Dec 14

Today

Project review

2:00 PM with the design team

Featured

New

Animation framework

Smoother, more dynamic transitions

Siri suggestions

Add Safina

@saffina28 on Instagram

Notification

Eyebrow flips to contrast

Same auto-contrast cascade as heading and subheading.

Small

Compact eyebrow

Medium

Default eyebrow

Large

Loud eyebrow

---
import Banner from "../Banner.astro";
import Badge from "@pindoba/astro-badge";
import Stamp from "@pindoba/astro-stamp";
import { Icon } from "astro-icon/components";
import { stack } from "@pindoba/styled-system/patterns";
---

<div class={stack({ gap: "xl", direction: "column", width: "100%" })}>
  <Banner eyebrow="Wednesday" heading="Dec 14" />

  <Banner
    eyebrow="Today"
    heading="Project review"
    subheading="2:00 PM with the design team"
  >
    <Stamp
      slot="eyebrow-leading"
      size="2xs"
      shape="square"
      feedback="neutral"
      border="none"
    >
      <Icon name="lucide:calendar" />
    </Stamp>
  </Banner>

  <Banner
    eyebrow="Featured"
    heading="Animation framework"
    subheading="Smoother, more dynamic transitions"
  >
    <span slot="eyebrow-trailing">
      <Badge size="sm" feedback="primary">New</Badge>
    </span>
  </Banner>

  <Banner
    eyebrow="Siri suggestions"
    heading="Add Safina"
    subheading="@saffina28 on Instagram"
  >
    <Stamp
      slot="eyebrow-leading"
      size="2xs"
      shape="square"
      feedback="neutral"
      border="none"
    >
      <Icon name="lucide:sparkles" />
    </Stamp>
  </Banner>

  <Banner
    feedback="primary"
    emphasis="primary"
    background="surface.soft"
    padding="md"
    radius="md"
    eyebrow="Notification"
    heading="Eyebrow flips to contrast"
    subheading="Same auto-contrast cascade as heading and subheading."
  >
    <Stamp
      slot="eyebrow-leading"
      size="2xs"
      shape="square"
      emphasis="adaptive"
      border="none"
    >
      <Icon name="lucide:bell" />
    </Stamp>
  </Banner>

  <Banner size="sm" eyebrow="Small" heading="Compact eyebrow" />
  <Banner size="md" eyebrow="Medium" heading="Default eyebrow" />
  <Banner size="lg" eyebrow="Large" heading="Loud eyebrow" />
</div>

Composing with Stamp

Banner’s slots compose cleanly with Stamp — use them at the top level (leading, trailing) or inside the heading itself via the { leading, content, trailing } shape.

Inbox

12 new notifications

Featured item

Stamps composed inline with the heading

---
import Banner from "../Banner.astro";
import Stamp from "@pindoba/astro-stamp";
import { stack } from "@pindoba/styled-system/patterns";
import { Icon } from "astro-icon/components";
---

<div class={stack({ gap: "xl", direction: "column" })}>
  <Banner heading="Inbox" subheading="12 new notifications">
    <Stamp slot="leading" background="surface.deep" shape="square" size="lg">
      <Icon name="lucide:inbox" />
    </Stamp>
    <Stamp
      slot="trailing"
      feedback="success"
      emphasis="secondary"
      shape="circle"
    >
      <Icon name="lucide:check" />
    </Stamp>
  </Banner>

  <Banner
    heading="Featured item"
    subheading="Stamps composed inline with the heading"
  >
    <Stamp
      slot="heading-leading"
      size="sm"
      background="surface.deep"
      feedback="warning"
    >
      <Icon name="lucide:star" />
    </Stamp>
    <Stamp slot="heading-trailing" size="sm" feedback="danger" shape="circle">
      <Icon name="lucide:bell" />
    </Stamp>
  </Banner>
</div>

Sizes

Three sizes control the heading font size. Padding/radius come from Panel and can be set independently.

Small

size="sm"

Medium

size="md"

Large

size="lg"

---
import Banner from "../Banner.astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div class={stack({ gap: "lg", direction: "column" })}>
  <Banner size="sm" heading="Small" subheading='size="sm"' />
  <Banner size="md" heading="Medium" subheading='size="md"' />
  <Banner size="lg" heading="Large" subheading='size="lg"' />
</div>

Feedback Surface

Banner inherits Panel’s feedback surfaces, useful when the banner itself is the component (not wrapped by a card or dialog).

Primary

Inherits panel surface from `feedback`

Success

Inherits panel surface from `feedback`

Warning

Inherits panel surface from `feedback`

Danger

Inherits panel surface from `feedback`

---
import Banner from "../Banner.astro";
import { stack } from "@pindoba/styled-system/patterns";
---

<div class={stack({ gap: "md", direction: "column" })}>
  <Banner
    feedback="primary"
    padding="md"
    radius="lg"
    heading="Primary"
    subheading="Inherits panel surface from `feedback`"
  />
  <Banner
    feedback="success"
    padding="md"
    radius="lg"
    heading="Success"
    subheading="Inherits panel surface from `feedback`"
  />
  <Banner
    feedback="warning"
    padding="md"
    radius="lg"
    heading="Warning"
    subheading="Inherits panel surface from `feedback`"
  />
  <Banner
    feedback="danger"
    padding="md"
    radius="lg"
    heading="Danger"
    subheading="Inherits panel surface from `feedback`"
  />
</div>

Props

props · 13 total
prop type default req description
eyebrow stringSnippet{ leading, content, trailing } undefined Small kicker line above the heading. Case-preserving and muted by default; auto-flips to contrast under emphasis="primary". Apply text-transform via passThrough.eyebrow.style if you want an uppercase…

eyebrow

Small kicker line above the heading. Case-preserving and muted by default; auto-flips to contrast under emphasis="primary". Apply text-transform via passThrough.eyebrow.style if you want an uppercase look. Accepts the same shape as heading.

Type string | Snippet | { leading, content, trailing }
Default undefined
Required No
heading stringSnippet{ leading, content, trailing } undefined Title content. Accepts a plain string, a snippet/slot, or an object with { leading, content, trailing } to add title-level flanking slots.
subheading stringSnippet{ leading, content, trailing } undefined Secondary title content, same shape as `heading`.
leading stringSnippetslot undefined Top-level leading slot rendered before the heading group. Typical use: a Stamp or avatar.
trailing stringSnippetslot undefined Top-level trailing slot pushed to the end of the row. Typical use: action buttons or a close affordance.
size "sm""md""lg" "md" Controls heading font size. sm, md, lg.
feedback "primary""success""warning""danger""neutral" "neutral" Semantic surface color inherited from Panel. When set to anything other than `neutral`, banner owns its background via the feedback palette.
padding "none""xs""sm""md""lg""xl" "none" Panel padding token. Defaults to none so banner reads flat inside cards/dialogs.
radius "none""xs""sm""md""lg""xl""2xl""full" "none" Panel radius token.
background "surface.soft""surface.step.1""surface.step.2""surface.step.3""surface.deep""transparent" "transparent" Panel surface fill. Only meaningful when feedback="neutral".
border "none""default""bold""muted" "none" Panel border ring strength. Only meaningful when feedback="neutral".
translucent boolean false Frosted-glass backdrop blur. Only meaningful when feedback="neutral".
passThrough { [slot]?: { style?: SystemStyleObject; props?: HTMLAttributes } } undefined Per-slot style/prop overrides. Keys: root, leading, trailing, headingGroup, eyebrowContainer, eyebrow, eyebrowLeading, eyebrowTrailing, headingContainer, heading, headingLeading, headingTrailing, subh…

passThrough

Per-slot style/prop overrides. Keys: root, leading, trailing, headingGroup, eyebrowContainer, eyebrow, eyebrowLeading, eyebrowTrailing, headingContainer, heading, headingLeading, headingTrailing, subheadingContainer, subheading, subheadingLeading, subheadingTrailing.

Type { [slot]?: { style?: SystemStyleObject; props?: HTMLAttributes } }
Default undefined
Required No