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.
A minimal banner with only a heading.
---
import Banner from "../Banner.astro";
---
<Banner heading="Project settings" />Add a subheading for a two-line title stack.
Manage workspace access, billing, and integrations
---
import Banner from "../Banner.astro";
---
<Banner
heading="Project settings"
subheading="Manage workspace access, billing, and integrations"
/>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
Today
2:00 PM with the design team
Featured
Smoother, more dynamic transitions
Siri suggestions
@saffina28 on Instagram
Notification
Same auto-contrast cascade as heading and subheading.
Small
Medium
Large
---
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>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.
12 new notifications
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>Three sizes control the heading font size. Padding/radius come from Panel and can be set independently.
size="sm"
size="md"
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>Banner inherits Panel’s feedback surfaces, useful when the banner itself is the component (not wrapped by a card or dialog).
Inherits panel surface from `feedback`
Inherits panel surface from `feedback`
Inherits panel surface from `feedback`
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>| 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…
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 Default Required | |
| 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…
Per-slot style/prop overrides. Keys: root, leading, trailing, headingGroup, eyebrowContainer, eyebrow, eyebrowLeading, eyebrowTrailing, headingContainer, heading, headingLeading, headingTrailing, subheadingContainer, subheading, subheadingLeading, subheadingTrailing. Type Default Required |