# 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.

- ID: `cmp_footer_minimal_01`
- Slug: `footer-minimal-01`
- Version: `1.0.0` (current)
- Status: published
- Published: 2026-09-30T23:46:34Z
- Updated: 2026-09-30
- Available versions: `1.0.0`
- Kind: section
- Primary category: `footers`
- Detail page: https://pagesugar.com/components/footer-minimal-01?variant=dark
- Preview: https://pagesugar.com/preview/footer-minimal-01

## Variants

| Variant | Label | Default | Artifact digest |
| --- | --- | --- | --- |
| `neutral` | Neutral | yes | `sha256-1253cefb602cac5bd8afc3e33a78e3dad2468d0d06962fe521e69c425cb5efe2` |
| `dark` | Dark band | no | `sha256-34865a48fda2a6fa9c263bca8c3d6e28104890c5019621e6a3886361f85e0c8c` |

## Runtime and compatibility

- Runtime: svelte
- Svelte: 5
- SvelteKit required: no (portable Svelte component)
- Tailwind CSS: 4
- SSR: supported
- Requires client-side JavaScript: no
- Integration level: presentational
- Appearance modes: light
- Suggested directory: `src/lib/components/footer-minimal-01`

## Dependencies

No third-party runtime packages.

## Services

No external services required.

## Usage

Presentational 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.

Required props: `brand`, `copyright`

```svelte
<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."
/>
```

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.

## Usage guide

### Filling it in

Copy `FooterMinimal.svelte` into `src/lib/components/footer-minimal-01/`. The `FooterLink` type is exported from its module script.

- `brand` is your organisation's name as a string, or a snippet holding your logo. A snippet renders as given, inside the brand link when `brandHref` is set and inside a plain `div` otherwise. Keep it free of links and buttons of its own, and give any image a text alternative (or keep the mark `aria-hidden` and put the name beside it as text) so the link has a name.
- `links` are the main destinations. Four to six read best; past about eight, use a multi-column footer.
- `legalLinks` are the policy pages. They sit in the small print, in their own navigation named `legalLabel`.
- `copyright` is 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:

```svelte
<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:

```svelte
{#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

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `brand` | string \| Snippet | yes |  | 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 |  | Complete copyright text, year included, such as '© 2026 Example Co Ltd.'. The component never reads the clock. |
| `brandHref` | `string` | no |  | 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

Change 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-ink sets the brand and link hover; --footer-minimal-muted sets the links and small print. Keep muted at 4.5:1 against the surface.
- Hairline: --footer-minimal-hairline draws the footer's top edge and the line above the small print.
- Accent: --footer-minimal-accent is the focus ring and the text-selection fill, with --footer-minimal-on-accent as the selected text. Keep the accent at 3:1 against the surface.
- Surface: --footer-minimal-surface is 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.md has 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.

| Token | Public CSS variable |
| --- | --- |
| `accent` | `--footer-minimal-accent` |
| `onAccent` | `--footer-minimal-on-accent` |
| `ink` | `--footer-minimal-ink` |
| `muted` | `--footer-minimal-muted` |
| `hairline` | `--footer-minimal-hairline` |
| `surface` | `--footer-minimal-surface` |

## Accessibility

- 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') and legalLinks in a second nav named by legalLabel ('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 brandHref is 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 image aria-hidden and 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.

## License

- Declared source: MIT
- Default license approval is pending. See https://pagesugar.com/docs/license.

## Source

- Palette: Dark band (`dark`)
- Entry: `FooterMinimal.svelte`
- Suggested directory: `src/lib/components/footer-minimal-01`
- Files: 1
- Artifact digest: `sha256-34865a48fda2a6fa9c263bca8c3d6e28104890c5019621e6a3886361f85e0c8c`

Paths below are relative to the suggested directory. Copy the files as they are;
they import nothing from this site.

#### `FooterMinimal.svelte`

Role: entry · 6371 bytes · SHA-256 `585e445d7076c071e6ff004b59064a156a30bd9cf4236c0eb409b297ea0dc1e3`

```svelte
<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, #fafafa);
		--_on-accent: var(--footer-minimal-on-accent, #18181b);
		--_ink: var(--footer-minimal-ink, #fafafa);
		--_muted: var(--footer-minimal-muted, #a1a1aa);
		--_hairline: var(--footer-minimal-hairline, #ffffff1a);
		--_surface: var(--footer-minimal-surface, #09090b);
	}

	/*
	 * 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>
```

## Artifacts

### Dark band (`dark`)

- Artifact digest: `sha256-34865a48fda2a6fa9c263bca8c3d6e28104890c5019621e6a3886361f85e0c8c`
- Entry: `FooterMinimal.svelte`
- Receipt: https://pagesugar.com/artifacts/cmp_footer_minimal_01/1.0.0/dark/sha256-34865a48fda2a6fa9c263bca8c3d6e28104890c5019621e6a3886361f85e0c8c/manifest.json
- Bundle: https://pagesugar.com/artifacts/cmp_footer_minimal_01/1.0.0/dark/sha256-34865a48fda2a6fa9c263bca8c3d6e28104890c5019621e6a3886361f85e0c8c/bundle.zip (4888 bytes, sha256 `94db266e11f307c96dbb97d8bac86831f6b7bca4f61632ad21c5388d949019e1`)

Files:

- `FooterMinimal.svelte` (entry, 6371 bytes): https://pagesugar.com/artifacts/cmp_footer_minimal_01/1.0.0/dark/sha256-34865a48fda2a6fa9c263bca8c3d6e28104890c5019621e6a3886361f85e0c8c/source/FooterMinimal.svelte
