Footer
Site footer with auto-fitting multi-column link groups, optional logo, social links, and a bottom bar.
Import
import { Footer } from "@hyzer-labs/ui"Demo
default fills with the muted surface (--hz-color-surface-muted). minimal is transparent with tighter padding, so whatever sits behind it
shows through. bordered is a separate boolean prop (a top hairline), so it
composes with either variant. Each demo sits on a tinted backdrop that is not part of
Footer, so minimal has something to show through. Each column's links reuses Nav's NavItem shape (label/href/external/ariaCurrent).
const columns: FooterColumn[] = [
{
title: 'Product',
links: [{ label: 'Components', href: '/components' } /* … */]
},
{
title: 'Resources',
links: [{ label: 'GitHub', href: 'https://github.com/hyzerlabs/ui', external: true } /* … */]
},
{ title: 'Company', links: [{ label: 'About', href: '/about' } /* … */] }
];
<Footer {columns} headingLevel={3} />const columns: FooterColumn[] = [
{
title: 'Product',
links: [{ label: 'Components', href: '/components' } /* … */]
},
{
title: 'Resources',
links: [{ label: 'GitHub', href: 'https://github.com/hyzerlabs/ui', external: true } /* … */]
},
{ title: 'Company', links: [{ label: 'About', href: '/about' } /* … */] }
];
<Footer {columns} headingLevel={3} variant="minimal" />const columns: FooterColumn[] = [
{
title: 'Product',
links: [{ label: 'Components', href: '/components' } /* … */]
},
{
title: 'Resources',
links: [{ label: 'GitHub', href: 'https://github.com/hyzerlabs/ui', external: true } /* … */]
},
{ title: 'Company', links: [{ label: 'About', href: '/about' } /* … */] }
];
<Footer {columns} headingLevel={3} bordered />const columns: FooterColumn[] = [
{
title: 'Product',
links: [{ label: 'Components', href: '/components' } /* … */]
},
{
title: 'Resources',
links: [{ label: 'GitHub', href: 'https://github.com/hyzerlabs/ui', external: true } /* … */]
},
{ title: 'Company', links: [{ label: 'About', href: '/about' } /* … */] }
];
<Footer {columns} headingLevel={3} variant="minimal" bordered />linkVariant restyles every column link: subtle (muted,
default), default (standard link treatment), nav (nav-weight, primary on hover).
<Footer {columns} headingLevel={3} /><Footer {columns} headingLevel={3} linkVariant="default" />social is a plain snippet, so any icon works, including brand marks. The
library ships the full Lucide set (generic glyphs like globe, mail, and rss below). Brand marks such as GitHub or X are
bring-your-own: drop an SVG, an icon-font glyph, or a package like simple-icons straight into the snippet.
<Footer {columns} headingLevel={3}>
{#snippet logo()}<strong>@hyzer-labs/ui</strong>{/snippet}
{#snippet social()}
<Link href="https://hyzer.sh" ariaLabel="Website"><IconGlobe /></Link>
<Link href="mailto:hello@hyzer.sh" ariaLabel="Email"><IconMail /></Link>
<Link href="/rss.xml" ariaLabel="RSS feed"><IconRss /></Link>
{/snippet}
{#snippet bottom()}
<p>© 2026 Hyzer Labs, MIT License</p>
{/snippet}
</Footer>Columns auto-fit the footer's own width: you get as many as have at least --hz-footer-col-min of room (12rem, and yours to retune through that custom property).
No breakpoints involved. Use the slider to watch them stack.
3 columns
<!-- Columns auto-fit the footer’s own width: as many as have at least
--hz-footer-col-min (12rem, yours to retune) of room -->
<Footer {columns} headingLevel={3} />Footer has no dedicated background hook. class merges after hz-footer, and your unlayered CSS beats the theme without needing !important. A low-percentage color-mix tint keeps the existing text
color's contrast intact.
<Footer {columns} headingLevel={3} class="brand-footer" />
<style>
:global(.brand-footer) {
background: color-mix(in srgb, var(--hz-intent-primary) 10%, var(--hz-color-surface));
}
</style>Props
| Name | Type | Default | Note |
|---|---|---|---|
columns | FooterColumn[] | — | Required. See FooterColumn below. |
variant | 'default' | 'minimal' | 'default' | |
bordered | boolean | false | Top hairline — composes with any variant. |
linkVariant | 'default' | 'subtle' | 'nav' | 'subtle' | Passed through to every column Link. |
headingLevel | 2 | 3 | 4 | 5 | 6 | 2 | Heading element for column titles — match your page hierarchy. |
logo | Snippet | — | Rendered above the columns. |
social | Snippet | — | Icon-link row rendered under the columns. |
bottom | Snippet | — | Bottom bar with a top hairline — copyright, legal links. |
class | string | — | Merged after the hz-footer class. |
FooterColumn
| Name | Type | Default | Note |
|---|---|---|---|
title | string | — | Required. Labels the column’s nav landmark. |
links | NavItem[] | — | Required. label/href/external/ariaCurrent apply — see NavItem on the Nav page. |
Theme hooks
What this component promises your CSS. The reference theme styles exactly these — from @layer hz-theme, so your unlayered rules win. See Styling Components for the how.
Root class: .hz-footer
Data attributes
| Hook | Values | Styles |
|---|---|---|
data-variant | 'default' | 'minimal' | minimal drops the padding and goes transparent. Default default. |
data-bordered | present when bordered | Top hairline. A prop, not a variant — it composes with either surface. |
Custom properties
| Hook | Values | Styles |
|---|---|---|
--hz-footer-col-min | <length> — default 12rem | Minimum column width for the auto-fit link grid: how narrow a column may get before the row reflows. No media queries involved — this is the whole knob. |
Part classes
| Hook | Values | Styles |
|---|---|---|
.hz-footer-logo | child element | Wrapper for the logo snippet. |
.hz-footer-columns | on a Grid | Rides through Grid’s class prop. The auto-fit override hangs off it — and lands on .hz-grid-layout, since that is where Grid does its layout. |
.hz-footer-column | child element | One column’s nav landmark. |
.hz-footer-heading | child element | A column heading. |
.hz-footer-social | child element | Wrapper for the social snippet. |
.hz-footer-bottom | child element | The bottom bar. |
Accessibility
Each column is a <nav> landmark whose aria-label is its title. The visible heading repeats that text, but the heading is not what names the landmark. Set headingLevel to match your page hierarchy. Links use the Link component, and icon-only social links need an ariaLabel.