Blocks / Marketing

Site footer

A marketing site footer: brand, a newsletter sign-up, four columns of links, the legal row and social links.

Width

Install

Writes the block to components/blocks/site-footer.tsx and its .css, and every part it uses to components/equip/, with the parts those are built from.

npx https://equip.studio311.app/equip.tgz add block:site-footer

Built from 5 parts

Notes

  • The newsletter is a Form with a function action; replace the simulated wait with your mailing list call and return { errors } for a refused address.
  • The column titles are h2 by default; pass headingLevel to Footer if your page outline needs another level.
  • The large name at the bottom is aria-hidden decoration; the brand link at the top is the one assistive tech reads.
  • Social icons are links named by their label, so each needs a label that says where it goes.

Source

components/blocks/site-footer.tsx155 lines
"use client";

import { useId } from "react";
import { Button } from "@/components/equip/button";
import { Field } from "@/components/equip/field";
import { Footer } from "@/components/equip/footer";
import { Form } from "@/components/equip/form";
import { Text } from "@/components/equip/text";
import "./site-footer.css";

const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

const glyph = {
  viewBox: "0 0 24 24",
  fill: "none",
  stroke: "currentColor",
  strokeWidth: 1.5,
  strokeLinecap: "round",
  strokeLinejoin: "round",
  "aria-hidden": true,
} as const;

/*
 * The bottom of a marketing site: the brand, a monthly letter to sign up for, four columns
 * of links and the legal row, with the name set large underneath like a shop sign.
 */
export default function SiteFooter() {
  const emailId = useId();

  return (
    <div className="blk-site-footer">
      <Footer
        className="blk-site-footer-main"
        maxWidth="76rem"
        inset="l"
        leadWidth="26rem"
        brand={
          <a href="#top" className="blk-site-footer-brand">
            <svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
              <path d="M7 4h10" />
              <path d="M8 4v2.5a3 3 0 0 1-1.5 2.6A3 3 0 0 0 5 11.7V18a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-6.3a3 3 0 0 0-1.5-2.6A3 3 0 0 1 16 6.5V4" />
              <path d="M5 13h14" />
            </svg>
            Larder
          </a>
        }
        blurb="Stock, ordering and waste tracking for independent restaurants, bakeries and cafes. Made in Hackney, by people who have closed a kitchen at midnight."
        columns={[
          {
            title: "Product",
            links: [
              { label: "Stock counts", href: "#stock-counts" },
              { label: "Supplier ordering", href: "#supplier-orders" },
              { label: "Waste reports", href: "#waste-reports" },
              { label: "Pricing", href: "#pricing" },
              { label: "Changelog", href: "#changelog" },
            ],
          },
          {
            title: "Kitchens",
            links: [
              { label: "Restaurants", href: "#restaurants" },
              { label: "Bakeries", href: "#bakeries" },
              { label: "Cafes", href: "#cafes" },
              { label: "Groups of sites", href: "#groups" },
              { label: "Customer stories", href: "#stories" },
            ],
          },
          {
            title: "Company",
            links: [
              { label: "About", href: "#about" },
              { label: "Careers", href: "#careers" },
              { label: "Press", href: "#press" },
              { label: "Contact", href: "#contact" },
            ],
          },
          {
            title: "Help",
            links: [
              { label: "Help centre", href: "#help" },
              { label: "Kitchen guides", href: "#guides" },
              { label: "Shelf kit", href: "#shelf-kit" },
              { label: "System status", href: "#status" },
            ],
          },
        ]}
        legal={
          <>
            <span>© 2026 Larder Ltd</span>
            <span>Registered in England and Wales, company number 14820357</span>
            <a href="#privacy">Privacy</a>
            <a href="#terms">Terms</a>
            <a href="#cookies">Cookie settings</a>
          </>
        }
        social={[
          {
            label: "Larder on Instagram",
            href: "#instagram",
            icon: (
              <svg {...glyph} width="20" height="20">
                <rect x="4" y="4" width="16" height="16" rx="4.5" />
                <circle cx="12" cy="12" r="3.5" />
                <path d="M16.5 7.5v.01" />
              </svg>
            ),
          },
          { label: "Email the Larder team", href: "#contact", icon: "mail" },
          { label: "Changelog feed", href: "#changelog-feed", icon: "rss" },
        ]}
      >
        <div className="blk-site-footer-letter">
          <Text size="s" weight="strong" className="blk-site-footer-letter-title">
            The Pass
          </Text>
          <Text size="s" tone="quiet">
            A monthly letter on running a tighter kitchen. One email, the first Tuesday of the month.
          </Text>
          <Form
            className="blk-site-footer-form"
            aria-label="Sign up for The Pass"
            successMessage="You are on the list. The next letter goes out on Tuesday 1 December."
            resetOnSuccess
            action={async () => {
              await wait(900);
            }}
          >
            {({ errors, pending }) => (
              <Field
                id={emailId}
                name="email"
                type="email"
                autoComplete="email"
                label="Email address"
                placeholder="ines@marrow.co.uk"
                required
                error={errors.email}
                action={
                  <Button type="submit" loading={pending}>
                    Subscribe
                  </Button>
                }
              />
            )}
          </Form>
        </div>
      </Footer>

      <p className="blk-site-footer-sign" aria-hidden="true">
        Larder
      </p>
    </div>
  );
}
components/blocks/site-footer.css60 lines
@layer blocks {
  .blk-site-footer {
    container: blk-site-footer / inline-size;
    overflow: clip;
    border-top: 1px solid var(--eq-line);
    background: var(--eq-bg);
    color: var(--eq-ink);
  }

  /* Width, inline padding and the lead column come from the Footer's maxWidth, inset and leadWidth. */
  .blk-site-footer-main {
    padding-block: calc(var(--eq-space-6) * 1.5) var(--eq-space-5);
    border-top: 0;
  }

  .blk-site-footer-brand {
    display: inline-flex;
    align-items: center;
    gap: var(--eq-space-2);
  }

  /* The letter: a small framed slip under the blurb, like a note pinned by the till. */
  .blk-site-footer-letter {
    display: grid;
    gap: var(--eq-space-1);
    align-self: stretch;
    margin-top: var(--eq-space-3);
    padding: var(--eq-space-4);
    border: 1px solid var(--eq-line);
    border-radius: var(--eq-radius-l);
    background: var(--eq-surface);
  }

  .blk-site-footer-letter p {
    margin: 0;
  }

  .blk-site-footer-letter-title {
    font-size: var(--eq-text-m);
  }

  .blk-site-footer-form {
    margin-top: var(--eq-space-2);
  }

  /* The name set large and cut off by the bottom edge, like a painted shop sign. */
  .blk-site-footer-sign {
    margin: 0 0 -0.14em;
    padding-inline: var(--eq-space-4);
    color: color-mix(in oklab, var(--eq-ink) 7%, transparent);
    font-family: var(--eq-font);
    font-size: clamp(4rem, 26cqi, 22rem);
    font-weight: var(--eq-weight-strong);
    line-height: 0.8;
    letter-spacing: -0.055em;
    text-align: center;
    white-space: nowrap;
    user-select: none;
  }
}

EqUIp is in beta: free for non-commercial use, no warranty, and things can change. Terms

Nothing picked yet. Press A on a part, or use the plus on the board, and the install command builds here.

Loading the index.

Loading the index