Blocks / Marketing
Site footer
A marketing site footer: brand, a newsletter sign-up, four columns of links, the legal row and social links.
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-footerBuilt 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;
}
}