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.

Source on GitHub

Usage

name is the copyright holder and defaults to "Example Site".

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" data-testid="site-footer">
  <div class="row">
    <nav aria-label="Footer" data-testid="site-footer-nav">
      <script data-bascik-build-once type="text/typescript">
        import { footerLinks } from "./links.ts";
        export default () => footerLinks();
      </script>
    </nav>
    <p class="copyright">
      &copy;
      <script data-bascik-build-once type="text/typescript">
        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.
// The markup is built with html from @bascik/bascik/html, which escapes each label and href for where it lands and
// turns a javascript: link into about:invalid, so there is no escaper to write.
import { html, type SafeHtml } from "@bascik/bascik/html";

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" },
];

/** The footer link list. */
export function footerLinks(links: FooterLink[] = FOOTER_LINKS): SafeHtml {
  const items = links.map(
    (link) => html`<li><a href="${link.href}">${link.label}</a></li>`,
  );
  // An array of html values is inserted as it is: no .join("").
  return html`<ul class="links">
    ${items}
  </ul>`;
}

How It Works

  • The footer is the same on every page, so both of its build scripts are data-bascik-build-once: each runs once, when Bascik registers the component, and every page shares the output. Compare the Site Header, whose data-bascik-build script reads BASCIK_PAGE_PATH to mark a different link on each page.
  • 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. Build script output is cached, so add src/components/site-footer/** to scripts.cache.exclude to print a new year when no file changed.
  • 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 to src/lib/ and import it as @/lib/links.ts.