Minimal footer
A single-band site footer: the brand and a short row of links on one line, with the copyright and policy links beneath a hairline on the same two edges. Inline or centred.
cmp_footer_minimal_01 Preview
Fit to the available width. The frame follows the height of its content; previews taller than the maximum auto-height scroll inside it.
Give this component to your coding agent Copy a prompt that fetches this exact version and palette through the PageSugar MCP server.
Using the PageSugar MCP server, fetch component cmp_footer_minimal_01 version 1.0.0 with variant "neutral", first inspect its requirements and license status and confirm this project uses Svelte 5 and Tailwind CSS 4. Retrieve every manifest file, including binary assets and any manifest-only response files, preserving relative paths. Then integrate the source and follow its usage notes. Run project checks, review the browser result and report anything unverified. Do not substitute another version or invent missing files.Not connected yet? Set up the MCP server
Code
- Palette
- Neutral
- Version
- 1.0.0
- Digest
Full digest
sha256-1253cefb602cac5bd8afc3e33a78e3dad2468d0d06962fe521e69c425cb5efe2
<script lang="ts" module>
export interface FooterLink {
label: string;
href: string;
}
</script>
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
/** The organisation's name as text, or your own logo snippet (give its image a text alternative). */
brand: string | Snippet;
/** Where the brand links, usually the home page. Omitted, the brand is not a link. */
brandHref?: string;
/** Complete copyright text, year included. The component never reads the clock. */
copyright: string;
/** A handful of main destinations; four to six read best. */
links?: FooterLink[];
/** Policy destinations such as Privacy and Terms, set in the small print. */
legalLinks?: FooterLink[];
/** Brand at the start and links at the end, or everything centred and stacked. */
layout?: 'inline' | 'centered';
/** Accessible name of the navigation holding `links`. */
navLabel?: string;
/** Accessible name of the navigation holding `legalLinks`. */
legalLabel?: string;
}
let {
brand,
brandHref,
copyright,
links = [],
legalLinks = [],
layout = 'inline',
navLabel = 'Footer',
legalLabel = 'Legal'
}: Props = $props();
const centered = $derived(layout === 'centered');
/* With nothing to divide, the copyright joins the brand's row and the small-print tier goes. */
const bare = $derived(links.length === 0 && legalLinks.length === 0);
/*
* One link style for both navs: muted at rest, ink on hover. A 28 by 24 px minimum on a fine
* pointer, its row level with the brand's; on a coarse pointer every link is at least 44 by 44 px.
*/
const linkClass = $derived([
'inline-flex max-w-full min-h-11 min-w-11 items-center rounded-sm text-(--_muted) transition-colors duration-150 ease-[cubic-bezier(.2,0,0,1)] hover:text-(--_ink) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--_accent) pointer-fine:min-h-7 pointer-fine:min-w-6',
centered && 'justify-center'
]);
</script>
{#snippet anchor(link: FooterLink)}
<a href={link.href} class={linkClass}>
<span class="min-w-0 [overflow-wrap:anywhere]">{link.label}</span>
</a>
{/snippet}
{#snippet brandBlock()}
{#if brandHref}
<a
href={brandHref}
class="inline-flex min-h-11 max-w-full min-w-11 items-center rounded-sm text-(--_ink) transition-colors duration-150 ease-[cubic-bezier(.2,0,0,1)] hover:text-(--_muted) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--_accent) pointer-fine:min-h-7"
>
{@render brandMark()}
</a>
{:else}
<div class="flex min-h-7 max-w-full items-center text-(--_ink)">
{@render brandMark()}
</div>
{/if}
{/snippet}
{#snippet brandMark()}
{#if typeof brand === 'string'}
<span
class="min-w-0 text-lg leading-7 font-semibold tracking-[-0.015em] text-balance [overflow-wrap:anywhere] rtl:tracking-normal"
>
{brand}
</span>
{:else}
{@render brand()}
{/if}
{/snippet}
{#snippet copyrightLine()}
<p class="max-w-[30em] min-w-0 text-balance [overflow-wrap:anywhere] tabular-nums">
{copyright}
</p>
{/snippet}
{#snippet legalNav()}
<nav aria-label={legalLabel} class="min-w-0">
<ul role="list" class={['flex flex-wrap gap-x-6', centered && 'justify-center']}>
{#each legalLinks as link, index (index)}
<li class="min-w-0">{@render anchor(link)}</li>
{/each}
</ul>
</nav>
{/snippet}
<footer
class="footer-minimal border-t border-(--_hairline) bg-(--_surface) px-4 py-12 text-start sm:px-6 sm:py-16 lg:px-8"
>
<div class="mx-auto max-w-6xl">
{#if centered}
<div class="flex flex-col items-center gap-6 text-center">
{@render brandBlock()}
{#if links.length}
<nav aria-label={navLabel} class="max-w-full min-w-0">
<ul role="list" class="flex flex-wrap justify-center gap-x-6 text-sm leading-5">
{#each links as link, index (index)}
<li class="min-w-0">{@render anchor(link)}</li>
{/each}
</ul>
</nav>
{/if}
{#if bare}
<div class="text-[0.8125rem] leading-5 text-(--_muted)">
{@render copyrightLine()}
</div>
{/if}
</div>
{#if !bare}
<div
class="mt-8 flex flex-col items-center gap-3 border-t border-(--_hairline) pt-8 text-center text-[0.8125rem] leading-5 text-(--_muted)"
>
{#if legalLinks.length}{@render legalNav()}{/if}
{@render copyrightLine()}
</div>
{/if}
{:else}
<!-- Brand and destinations share a 28 px first row, so their first lines sit level. -->
<div class="flex flex-col items-start gap-6 md:flex-row md:justify-between md:gap-8">
<div class="max-w-full min-w-0 md:max-w-sm md:shrink-0">
{@render brandBlock()}
</div>
{#if bare}
<div class="text-[0.8125rem] leading-5 text-(--_muted) md:leading-7">
{@render copyrightLine()}
</div>
{:else if links.length}
<nav aria-label={navLabel} class="w-full min-w-0 md:w-auto md:flex-1">
<ul
role="list"
class="grid grid-cols-2 gap-x-6 text-sm leading-5 sm:flex sm:flex-wrap md:justify-end"
>
{#each links as link, index (index)}
<li class="min-w-0">{@render anchor(link)}</li>
{/each}
</ul>
</nav>
{/if}
</div>
{#if !bare}
<!-- The small print repeats the row's two edges: copyright under the brand, policies under the links. -->
<div
class="mt-8 flex flex-col items-start gap-3 border-t border-(--_hairline) pt-8 text-[0.8125rem] leading-5 text-(--_muted) md:flex-row md:items-baseline md:justify-between md:gap-8"
>
{@render copyrightLine()}
{#if legalLinks.length}{@render legalNav()}{/if}
</div>
{/if}
{/if}
</div>
</footer>
<style>
/* Public tokens: set --footer-minimal-* on this footer or any ancestor to retone it. */
.footer-minimal {
--_accent: var(--footer-minimal-accent, #18181b);
--_on-accent: var(--footer-minimal-on-accent, #ffffff);
--_ink: var(--footer-minimal-ink, #18181b);
--_muted: var(--footer-minimal-muted, #52525b);
--_hairline: var(--footer-minimal-hairline, rgb(0 0 0 / 0.08));
--_surface: var(--footer-minimal-surface, transparent);
}
/*
* Selected text takes the accent pair, so a copied company name reads in the brand's colours.
* CSS, because it styles every element the footer renders rather than one.
*/
.footer-minimal ::selection {
background-color: var(--_accent);
color: var(--_on-accent);
}
</style>
Usage#
On this pagePresentational only: the brand, links and copyright are rendered as supplied. It does not write policy content, include social links (compose a social links group beside it), check that links resolve or work out the copyright year; pass the complete copyright text. No runtime packages beyond Svelte and Tailwind CSS.
- Suggested location
src/lib/components/footer-minimal-01
Limitations
- Built for a handful of links. Past about eight destinations the row wraps onto several lines; a multi-column footer suits a larger set.
- With no links and no legal links, the copyright moves onto the brand's row and the hairline is dropped.
- Every link opens in the same tab; there is no external-link treatment.
- Light appearance by default with a transparent surface, so it takes the page's background. The dark palette retones it through its variables; no dark mode is declared or selected automatically.
Example
<script lang="ts">
import FooterMinimal from '$lib/components/footer-minimal-01/FooterMinimal.svelte';
</script>
<FooterMinimal
brand="Example Co"
brandHref="/"
links={[
{ label: 'Features', href: '/features' },
{ label: 'Pricing', href: '/pricing' },
{ label: 'Contact', href: '/contact' }
]}
legalLinks={[
{ label: 'Privacy', href: '/privacy' },
{ label: 'Terms', href: '/terms' }
]}
copyright="© 2026 Example Co Ltd."
/>Filling it in#
Copy FooterMinimal.svelte into src/lib/components/footer-minimal-01/. The FooterLink type is exported from its module script.
brandis your organisation's name as a string, or a snippet holding your logo. A snippet renders as given, inside the brand link whenbrandHrefis set and inside a plaindivotherwise. Keep it free of links and buttons of its own, and give any image a text alternative (or keep the markaria-hiddenand put the name beside it as text) so the link has a name.linksare the main destinations. Four to six read best; past about eight, use a multi-column footer.legalLinksare the policy pages. They sit in the small print, in their own navigation namedlegalLabel.copyrightis the whole string, year included. The component never computes the year, so the server and the browser always render the same text.
Leave out links and legalLinks and no navigation renders; the copyright joins the brand's row.
Layout#
| Width | layout="inline" (default) |
layout="centered" |
|---|---|---|
| under 640 | Brand, then links two to a row, then the copyright and policies, all at the start | Brand, links, policies and copyright on the centre line |
| 640 to 767 | As above, with the links in one wrapping row | As above |
| 768 and up | Brand at the start, links at the end; copyright and policies on the same edges | As above |
Retoning#
| Variable | Default | Draws |
|---|---|---|
--footer-minimal-ink |
#18181b |
Brand, link hover |
--footer-minimal-muted |
#52525b |
Links, copyright |
--footer-minimal-hairline |
rgb(0 0 0 / 0.08) |
Top edge, line above the small print |
--footer-minimal-accent |
#18181b |
Focus ring, selection fill |
--footer-minimal-on-accent |
#ffffff |
Selected text |
--footer-minimal-surface |
transparent |
Background (the page shows through it) |
A dark band:
<div
style="
--footer-minimal-surface: #09090b;
--footer-minimal-ink: #fafafa;
--footer-minimal-muted: #a1a1aa;
--footer-minimal-hairline: rgb(255 255 255 / 0.1);
--footer-minimal-accent: #fafafa;
--footer-minimal-on-accent: #18181b;
"
>
<FooterMinimal {brand} {links} {legalLinks} {copyright} />
</div>A logo beside the name:
{#snippet logo()}
<span class="inline-flex items-center gap-2 text-lg leading-7 font-semibold">
<img src="/logo.svg" alt="" class="size-6" />
Example Co
</span>
{/snippet}
<FooterMinimal brand={logo} brandHref="/" {copyright} />Props and content inputs#
On this page| Name | Type | Required | Default | Description |
|---|---|---|---|---|
brand | string | Snippet | Yes | None | The organisation's name as text, or your own logo snippet. The snippet renders inside the brand link (with brandHref) or a div, so keep it non-interactive and give a logo image a text alternative. |
copyright | string | Yes | None | Complete copyright text, year included, such as '© 2026 Example Co Ltd.'. The component never reads the clock. |
brandHref | string | No | None | Where the brand links, usually '/'. Omitted, the brand is plain content, not a link. |
links | FooterLink[] | No | [] | Main destinations as { label, href }. Four to six read best. Empty, no navigation renders. |
legalLinks | FooterLink[] | No | [] | Policy destinations such as Privacy and Terms, set in the small print. Empty, no legal navigation renders. |
layout | 'inline' | 'centered' | No | 'inline' | Brand at the start and links at the end from 768 px, stacked below; or everything centred and stacked. |
navLabel | string | No | 'Footer' | Accessible name of the navigation holding links. |
legalLabel | string | No | 'Legal' | Accessible name of the navigation holding legalLinks. Keep it different from navLabel. |
Customization#
On this pageChange content through props, retone the footer through six --footer-minimal-* variables (accent, on-accent, ink, muted, hairline, surface), and edit Tailwind classes in the source for spacing or the breakpoint.
- Text:
--footer-minimal-inksets the brand and link hover;--footer-minimal-mutedsets the links and small print. Keep muted at 4.5:1 against the surface. - Hairline:
--footer-minimal-hairlinedraws the footer's top edge and the line above the small print. - Accent:
--footer-minimal-accentis the focus ring and the text-selection fill, with--footer-minimal-on-accentas the selected text. Keep the accent at 3:1 against the surface. - Surface:
--footer-minimal-surfaceis the footer background, transparent by default so it takes the page's colour. - Dark band: surface
#09090b, ink#fafafa, muted#a1a1aa, hairline #ffffff1a, accent#fafafa, on-accent#18181b.usage.mdhas the snippet. - Cream page: leave surface transparent and set hairline to rgb(68 64 60 / 0.12) so the lines warm with the page.
- Logo: pass a snippet as brand for a mark beside the name; keep it about 24 px tall so it sits in the 28 px brand row.
- Layout: layout='centered' stacks everything on the centre line; the inline layout switches to one row at the md breakpoint (768 px).
- Copyright: pass the full string, including the year; the component does not compute it, so server and client renders always agree.
Public CSS variables
| Variable | Token |
|---|---|
--footer-minimal-accent | accent |
--footer-minimal-on-accent | onAccent |
--footer-minimal-ink | ink |
--footer-minimal-muted | muted |
--footer-minimal-hairline | hairline |
--footer-minimal-surface | surface |
Accessibility#
On this page- The root is a footer element, which is the contentinfo landmark when it is not inside an article, aside, main, nav or section.
- links sit in a nav named by
navLabel('Footer') andlegalLinksin a second nav named bylegalLabel('Legal'), each holding a list. A nav whose links are empty is not rendered. - No separator characters sit between links; spacing separates them, so screen readers read only the labels.
- Links are muted at rest (
#52525b, 7.7:1 on white) and turn ink on hover; focus shows a two-pixel accent outline offset by two pixels on :focus-visible only. - Every link, and the brand when linked, is at least 28 px tall on a fine pointer (WCAG 2.5.8 asks for 24) and at least 44 by 44 px on a coarse pointer.
- When brand is a snippet, it renders as given inside the brand link when
brandHrefis set, and inside a plain div otherwise. Keep it non-interactive (no links or buttons of its own) and give any logo image a text alternative, or mark the imagearia-hiddenand put the name beside it as text, so the link has a name. - The layout uses logical properties and mirrors under dir="rtl"; the brand name's tracking resets there.
Known limitations
- Contrast ratios are computed for the shipped palettes; re-check any retoned token (4.5:1 for text, 3:1 for the focus ring).
- The surface is transparent by default, so text contrast depends on your page background until you set
--footer-minimal-surface.
Release details#
On this page- Integration
- Presentational
- Works without client-side JavaScript
- Server-side rendering supported
- Dependencies
- No additional runtime packages beyond Svelte and Tailwind CSS
- License
MIT. Default license approval is pending; see the license status before adopting the source.
- Version history
- 1.0.0 (Published) Current release · 30 September 2026
Only the current release is available. Keep downloaded source and its receipt if you need to use it again later.