Example Components
Site Footer
A footer for every page: secondary links and a copyright line. The links and the year are written at build time, so the footer is plain HTML and ships no JavaScript.
Usage
name is the copyright holder and defaults to "Example Site".
true
examples/components/site-footer/site-footer.html
html
<site-footer data-bascik-prop-name="Example Site" /> html examples/components/site-footer/site-footer.html
<!-- @bascik
A site footer: secondary links and a copyright line. The links live in links.ts next to this file, and the year is written at build time, so the footer ships no JavaScript.
@prop name - Copyright holder. Defaults to "Example Site".
-->
<style>
.footer {
margin-top: var(--space-6, 4rem);
border-top: 1px solid var(--color-border, currentColor);
color: var(--color-muted, inherit);
font-size: 0.9rem;
}
.row {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: var(--space-3, 1rem);
max-width: var(--content-width, 70rem);
margin-inline: auto;
padding: var(--space-4, 1.5rem) var(--space-3, 1rem);
}
/* Two classes outrank a site-wide rule such as `.content ul` or `.content p`, so the resets below always apply. */
.footer .links {
display: flex;
flex-wrap: wrap;
gap: var(--space-3, 1rem);
margin: 0;
padding: 0;
list-style: none;
}
.links li {
margin: 0;
}
.links a {
color: inherit;
}
.footer .copyright {
margin: 0;
}
</style>
<footer class="footer">
<div class="row">
<nav aria-label="Footer">
<!-- The footer is the same on every page, so this runs once for the whole site and the output is reused. -->
<script data-bascik-build>
import { footerLinks } from './links.ts';
export default () => footerLinks();
</script>
</nav>
<p class="copyright">
©
<script data-bascik-build>
export default () => String(new Date().getFullYear());
</script>
<span data-bascik-prop-name>Example Site</span>
</p>
</div>
</footer> Output is shown unminified (HTML, CSS, JS, and identifier names) for readability.
links.ts
ts examples/components/site-footer/links.ts
// links.ts: the footer links. Edit this list for your site. site-footer.html prints it from a build script.
export interface FooterLink {
label: string;
href: string;
}
export const FOOTER_LINKS: FooterLink[] = [
{ label: 'About', href: '/about' },
{ label: 'Blog', href: '/blog/' },
{ label: 'Feed', href: '/feed.xml' },
{ label: 'Contact', href: '/contact' },
];
const ENTITIES: Record<string, string> = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
const escapeHtml = (value: string): string => value.replace(/[&<>"']/g, (ch) => ENTITIES[ch]);
/** The footer link list. */
export function footerLinks(links: FooterLink[] = FOOTER_LINKS): string {
const items = links.map((link) => `<li><a href="${escapeHtml(link.href)}">${escapeHtml(link.label)}</a></li>`);
return `<ul class="links">${items.join('')}</ul>`;
} How It Works
- The footer is the same on every page, so its build scripts are plain
data-bascik-build: each runs once for the whole site, and Bascik reuses the output on every page. Compare the Site Header, which needsdata-bascik-build="page"because each page marks a different link. - The year comes from a build script, so it is correct as of the last build. Rebuild at least once a year, or print it with a client
<script>if the site is rarely rebuilt. - The
<nav aria-label="Footer">label keeps it distinct from the header's navigation for screen reader users.
Adapt It
- For columns of links, return several labeled lists from
footerLinks()and lay them out with a grid. - To repeat the header's links, import them from
../site-header/links.ts, or move one shared list tosrc/lib/and import it as@/lib/links.ts.