Component
footer
Demo
tokens.css,
base.css and footer.css. No scripts.
Source
Copy both files, or run npx nojsui add footer.
Browser support
Works across current and earlier versions of every major engine.
| Feature | Baseline | Chrome | Edge | Firefox | Safari | Chrome Android | Firefox Android | Safari iOS |
|---|
Usage
A page footer with three slots: a brand mark, a row of link groups, and a legal bar. Each group is a heading plus a list of links; the legal bar carries a copyright line and whatever else belongs at the bottom of the page.
<footer class="sk-footer">
<p class="sk-footer__brand">Studio</p>
<div class="sk-footer__groups">
<div class="sk-footer__group">
<h3 class="sk-footer__heading" id="footer-product">Product</h3>
<ul class="sk-footer__list" aria-labelledby="footer-product">
<li><a class="sk-footer__link" href="/components/">Components</a></li>
</ul>
</div>
</div>
<div class="sk-footer__legal">
<span>© 2026 Studio</span>
<span>Built with zero JavaScript</span>
</div>
</footer>
How it works
Responsive by flex-wrap, not by a media query
.sk-footer__groups is a flex-wrap: wrap row and each .sk-footer__group
has a minimum flex basis (--sk-footer-group-min, 10rem by default). When
the row runs out of room for another group at that width, it wraps — there is
no breakpoint anywhere in this file.
A media query asks how wide the viewport is; a footer only ever needed to
know how wide it is. Wrapping at a content-derived minimum means the
component behaves the same in a sidebar as it does across a full page, and a
consumer tunes one length instead of maintaining a set of breakpoints.
.sk-card makes the same argument with container queries — this is the same
idea with the simpler tool for a case that does not need them: the groups row
never changes its own internal layout at a threshold, it just wraps, and
flex-wrap already does that natively.
<footer> is the landmark
The root element is a real <footer>, not a <div> with a class on it. That
is what makes it the contentinfo landmark — a <div> would cost you that
for nothing, and a screen reader user who jumps by landmark would lose the way
in to find it.
Each group’s list is named by its heading
.sk-footer__list carries aria-labelledby pointing at the id of the
.sk-footer__heading next to it — the same wiring .sk-section uses for its
own headings. Without it, three link stacks that all look like “a list of
links” are announced as three anonymous lists; with it, each is announced as
“Product, list” (or whatever its heading says), so the groups stay
distinguishable in a screen reader’s landmark/list navigation. The ids in the
demo are prefixed per instance so multiple demos on one page don’t collide.
Keyboard contract
The footer is not a control; every focusable element inside it (the links) keeps ordinary link behaviour and tab order.
| Key | Behaviour |
|---|---|
Tab | Moves focus through the links in source order |
Enter | Activates the focused link |
Verified manually in VoiceOver and NVDA: not yet — do this before marking the component done, and change this line when you have.
Accessibility notes
<footer> at the top level of the page (not nested in an <article> or
<section>) is exposed as the contentinfo landmark automatically; nothing
extra is needed for that.
Each group’s <ul> is named by aria-labelledby on its own heading rather
than a generic aria-label, so the visible heading text and the accessible
name never drift apart — change the heading and the name updates with it.
The heading level on .sk-footer__heading is the consumer’s to choose, not
fixed by the component — pick whatever level continues the page’s own
outline. The demo above uses h3; this kit’s own docs site uses h2,
because its 404 page has only an h1 above the footer, where an h3 would
skip a level and fail axe’s heading-order rule.
The legal bar’s two lines are plain <span> siblings, not a list; there is
nothing to enumerate.
Degradation
Flexbox (flex-wrap, flex-basis) is Baseline widely available across every
supported engine — there is nothing to degrade. The footer looks and behaves
identically from the oldest supported browser onward.
| Feature used | Baseline status | Behavior without it |
|---|---|---|
Flexbox (flex-wrap) | widely available | N/A — no browser in the support matrix lacks it |
Theming
| Property | Default | Controls |
|---|---|---|
--sk-footer-bg | --sk-color-surface | Footer background |
--sk-footer-fg | --sk-color-text | Brand and heading colour |
--sk-footer-link-fg | --sk-color-text-muted | Link colour at rest |
--sk-footer-border-color | --sk-color-border | The top edge and the legal divider |
--sk-footer-group-min | 10rem | Narrowest a link group gets before the row wraps |
--sk-footer-padding-inline | --sk-space-lg | Horizontal padding. Set it to the page’s own gutter so the footer’s content lines up with the sections above it |
--sk-footer-padding-block | --sk-space-2xl | Vertical padding |