Astro UI
Header Centered.
Brand in the middle, links split either side, optional announcement strip. Folds into a details menu.
Site chromeSectionNo dependencies Original, MIT
Style
Using Claude Code? Give it the agent prompt and skip these steps.
-
Import the foundation once
foundation.css holds the tokens every component reads.
--- // src/layouts/Layout.astro import "../styles/foundation.css"; --- -
Copy HeaderCentered.astro
Paste into
src/components/viewership/header-centered/ -
Use it
--- import HeaderCentered from "../components/viewership/header-centered/HeaderCentered.astro"; --- <HeaderCentered />
Props 8
| Prop | Type | Default | Description |
|---|---|---|---|
brand | string | · | Brand name, set as text unless the logo slot is used. |
brandHref | string | "/" | Where the brand links. |
left | { label: string; href: string }[] | · | Links left of the brand. |
right | { label: string; href: string }[] | [] | Links right of the brand. |
announcement | string | · | One line in a strip above the header. |
announcementHref | string | · | Makes the announcement a link. |
current | string | · | Href of the current page. |
slot: logo | slot | · | Replaces the text brand. |
Source 1 file
---
/**
* HeaderCentered: brand in the middle, links split either side, with an
* optional announcement strip above. Editorial and shop-front sites suit it.
* Folds into a native <details> menu when narrow.
*
* @example
* <HeaderCentered brand="Northwind" left={[{ label: "Shop", href: "/shop" }]}
* right={[{ label: "Account", href: "/account" }]} announcement="Free repairs this month" />
*/
interface NavLink {
label: string;
href: string;
}
interface Props {
brand: string;
brandHref?: string;
left: NavLink[];
right?: NavLink[];
/** One line in a strip above the header. */
announcement?: string;
announcementHref?: string;
current?: string;
class?: string;
}
const { brand, brandHref = "/", left, right = [], announcement, announcementHref, current, class: className } = Astro.props;
const all = [...left, ...right];
---
<header class:list={["vs-header-centered", className]}>
{
announcement && (
<p class="vs-header-centered__strip">{announcementHref ? <a href={announcementHref}>{announcement}</a> : announcement}</p>
)
}
<div class="vs-header-centered__inner">
<nav class="vs-header-centered__side" aria-label="Main">
<ul>
{
left.map((l) => (
<li>
<a href={l.href} aria-current={l.href === current ? "page" : undefined}>
{l.label}
</a>
</li>
))
}
</ul>
</nav>
<a class="vs-header-centered__brand" href={brandHref}>
<slot name="logo">{brand}</slot>
</a>
<nav class="vs-header-centered__side vs-header-centered__side--end" aria-label="Secondary">
<ul>
{
right.map((l) => (
<li>
<a href={l.href} aria-current={l.href === current ? "page" : undefined}>
{l.label}
</a>
</li>
))
}
</ul>
</nav>
<details class="vs-header-centered__menu">
<summary>Menu</summary>
<nav class="vs-header-centered__sheet" aria-label="Main">
<ul>
{
all.map((l) => (
<li>
<a href={l.href} aria-current={l.href === current ? "page" : undefined}>
{l.label}
</a>
</li>
))
}
</ul>
</nav>
</details>
</div>
</header>
<style>
.vs-header-centered {
container-type: inline-size;
position: relative;
z-index: 10;
background: var(--vs-page);
color: var(--vs-ink);
font-family: var(--vs-font-sans);
font-stretch: var(--vs-font-stretch);
border-bottom: calc(var(--vs-border-w) + 1px) solid var(--vs-ink);
}
.vs-header-centered a {
color: inherit;
text-decoration: none;
}
.vs-header-centered a:focus-visible,
.vs-header-centered summary:focus-visible {
outline: none;
box-shadow: var(--vs-focus-ring);
}
.vs-header-centered ul {
margin: 0;
padding: 0;
list-style: none;
}
.vs-header-centered__strip {
margin: 0;
padding: 8px var(--vs-gutter);
background: var(--vs-ink);
color: var(--vs-ink-inverse);
font-size: 0.8125rem;
text-align: center;
}
.vs-header-centered__strip a {
text-decoration: underline;
text-underline-offset: 3px;
}
.vs-header-centered__inner {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
min-height: 72px;
gap: 24px;
max-width: var(--vs-container);
margin-inline: auto;
padding: 0 var(--vs-gutter);
}
.vs-header-centered__brand {
font-family: var(--vs-font-display, var(--vs-font-sans));
font-size: 1.625rem;
font-weight: var(--vs-display-weight);
letter-spacing: var(--vs-display-tracking);
text-transform: var(--vs-display-transform);
}
.vs-header-centered__side {
display: none;
}
.vs-header-centered__side ul {
display: flex;
gap: 24px;
font-size: 0.875rem;
letter-spacing: 0.01em;
}
.vs-header-centered__side--end ul {
justify-content: flex-end;
}
.vs-header-centered__side a[aria-current="page"],
.vs-header-centered__side a:hover {
text-decoration: underline;
text-decoration-color: var(--vs-accent);
text-decoration-thickness: 2px;
text-underline-offset: 6px;
}
.vs-header-centered__menu {
justify-self: end;
}
.vs-header-centered__menu summary {
padding: 8px 0;
font-size: 0.875rem;
font-weight: 600;
text-decoration: underline;
text-underline-offset: 4px;
cursor: pointer;
list-style: none;
}
.vs-header-centered__menu summary::-webkit-details-marker {
display: none;
}
.vs-header-centered__sheet {
position: absolute;
top: 100%;
left: 0;
right: 0;
padding: 8px var(--vs-gutter) 20px;
background: var(--vs-page);
border-bottom: calc(var(--vs-border-w) + 1px) solid var(--vs-ink);
animation: vs-fade-in var(--vs-duration) var(--vs-ease-out);
}
.vs-header-centered__sheet a {
display: block;
padding: 12px 0;
border-bottom: 1px solid var(--vs-line);
font-size: 1.0625rem;
}
@container (min-width: 820px) {
.vs-header-centered__inner {
grid-template-columns: 1fr auto 1fr;
}
.vs-header-centered__side {
display: block;
}
.vs-header-centered__menu {
display: none;
}
}
</style> Original, MIT.