Skip to content
Download ZIP

Neutral palette · 4.7 KB ZIP File receipt View as Markdown View code

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.
cmp_section_wrapper_01 · version 1.0.0 · Neutral palette
Using the PageSugar MCP server, fetch component cmp_section_wrapper_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-dbe848d7690641a787819a6b7fdecc18628e9c31915d31932eb80027cdaaa9e6
Section.svelte Svelte · 4.9 KB Raw
<script lang="ts" module>
	export type SectionSpacing = 'none' | 'sm' | 'md' | 'lg';
	export type SectionTone = 'default' | 'muted' | 'inverse' | 'accent';
	export type SectionWidth = 'prose' | 'default' | 'wide' | 'full';
</script>

<script lang="ts">
	import type { Snippet } from 'svelte';

	interface Props {
		/** Anchor id, so `#id` links land on this section below the sticky header. */
		id?: string;
		/** Vertical padding preset. */
		spacing?: SectionSpacing;
		/** Background and text tone. */
		tone?: SectionTone;
		/** Inner container width. */
		width?: SectionWidth;
		/** Id of the heading inside that names this section. */
		labelledby?: string;
		/** The section's content. */
		children: Snippet;
	}

	let {
		id,
		spacing = 'md',
		tone = 'default',
		width = 'default',
		labelledby,
		children
	}: Props = $props();

	/*
	 * Section padding (DESIGN §3.3). md is the standard marketing section; sm is for a band that
	 * leans on its neighbours (a logo strip, a short CTA) and still clears 80 px on desktop.
	 */
	const padding: Record<SectionSpacing, string> = {
		none: 'py-0',
		sm: 'py-12 sm:py-16 lg:py-20',
		md: 'py-16 sm:py-24 lg:py-32',
		lg: 'py-20 sm:py-32 lg:py-40'
	};

	/*
	 * Gutters sit on the section and the measure on the container inside it, the same shape as the
	 * catalogue's own sections, so default content is 1152 px and every section on one width
	 * starts on the same edge. Prose is a narrower centred column for long reading. Full hands the
	 * whole width to its content, which brings its own gutters: a catalogue section that already
	 * pads and constrains itself, or media that runs edge to edge.
	 */
	const gutters = 'px-4 sm:px-6 lg:px-8';
	const measure: Record<SectionWidth, string> = {
		prose: 'mx-auto max-w-2xl',
		default: 'mx-auto max-w-6xl',
		wide: 'mx-auto max-w-7xl',
		full: ''
	};
</script>

<section
	{id}
	class={['section-wrapper', padding[spacing], width !== 'full' && gutters]}
	data-tone={tone}
	aria-labelledby={labelledby || undefined}
>
	<div class={['w-full', measure[width]]}>
		{@render children?.()}
	</div>
</section>

<style>
	/*
	 * Public tokens: set --section-wrapper-* on this section or any ancestor to retone it. Each
	 * tone resolves them into four --section-tone-* values that content inside can read, so a
	 * heading or a hairline follows the band it sits on.
	 */
	.section-wrapper {
		--_accent: var(--section-wrapper-accent, #18181b);
		--_on-accent: var(--section-wrapper-on-accent, #ffffff);
		--_ink: var(--section-wrapper-ink, #18181b);
		--_muted: var(--section-wrapper-muted, #52525b);
		--_hairline: var(--section-wrapper-hairline, rgb(0 0 0 / 0.08));
		--_surface: var(--section-wrapper-surface, transparent);
		--_muted-surface: var(--section-wrapper-muted-surface, #f4f4f5);
		--_inverse-surface: var(--section-wrapper-inverse-surface, #09090b);
		--_on-inverse: var(--section-wrapper-on-inverse, #fafafa);
		--_inverse-muted: var(--section-wrapper-inverse-muted, #a1a1aa);
		--_inverse-scheme: var(--section-wrapper-inverse-scheme, dark);
		--_accent-scheme: var(--section-wrapper-accent-scheme, dark);
		--_header-offset: var(--section-wrapper-header-offset, 0px);

		--section-tone-surface: var(--_surface);
		--section-tone-ink: var(--_ink);
		--section-tone-muted: var(--_muted);
		--section-tone-hairline: var(--_hairline);

		background-color: var(--section-tone-surface);
		color: var(--section-tone-ink);
		/* An anchor jump stops below the site's sticky header instead of under it. */
		scroll-margin-block-start: var(--_header-offset);
	}

	.section-wrapper[data-tone='muted'] {
		--section-tone-surface: var(--_muted-surface);
	}

	.section-wrapper[data-tone='inverse'] {
		--section-tone-surface: var(--_inverse-surface);
		--section-tone-ink: var(--_on-inverse);
		--section-tone-muted: var(--_inverse-muted);
		--section-tone-hairline: color-mix(in oklab, var(--_on-inverse) 12%, transparent);
		color-scheme: var(--_inverse-scheme);
	}

	.section-wrapper[data-tone='accent'] {
		--section-tone-surface: var(--_accent);
		--section-tone-ink: var(--_on-accent);
		--section-tone-muted: color-mix(in oklab, var(--_on-accent) 84%, var(--_accent));
		--section-tone-hairline: color-mix(in oklab, var(--_on-accent) 20%, transparent);
		color-scheme: var(--_accent-scheme);
	}

	/*
	 * Two neighbours on the same tone read as one band, so the second drops its top padding and
	 * the gap between them is the first one's bottom padding, not both. Its anchor stop keeps a
	 * little air above the heading that now sits at its top edge.
	 */
	:global(.section-wrapper[data-tone='default']) + .section-wrapper[data-tone='default'],
	:global(.section-wrapper[data-tone='muted']) + .section-wrapper[data-tone='muted'],
	:global(.section-wrapper[data-tone='inverse']) + .section-wrapper[data-tone='inverse'],
	:global(.section-wrapper[data-tone='accent']) + .section-wrapper[data-tone='accent'] {
		padding-block-start: 0;
		scroll-margin-block-start: calc(var(--_header-offset) + 2rem);
	}
</style>

Presentational layout only: it paints a tone, pads the section and constrains its content. It renders no heading, eyebrow or copy of its own; you pass those as children and hand their heading id to labelledby. It sets inherited defaults (text colour, and color-scheme on the dark tones) but never overrides colours your content or other components set explicitly.

Suggested location
src/lib/components/section-wrapper-01
Required props
children

Limitations

  • Renders no heading. Pass one as a child and give its id to labelledby, or the section stays an unnamed generic container.
  • Components placed inside keep their own colour tokens, surfaces included: on the inverse or accent tone, point every colour they paint at the tone (for example surface, ink, muted and hairline to var(--section-tone-surface), var(--section-tone-ink), var(--section-tone-muted) and var(--section-tone-hairline)). usage.md has a worked example.
  • The same-tone collapse uses the adjacent-sibling selector, so it only applies when two sections are direct siblings; a wrapper element or a hidden element between them keeps both paddings.
  • The header offset defaults to 0. Set --section-wrapper-header-offset to your sticky header's height, and do not also set html { scroll-padding-top }, or the two offsets add up. A section that follows a same-tone neighbour stops 2rem further down, since it has no top padding.
  • The offset is scroll-margin on the section, so it covers jumps to the section itself, not focus moving to a link or field inside it; use html { scroll-padding-top } instead if focused controls can land under the header.
  • In the neutral palette the accent tone is near-black, close to the inverse tone; the accent tone becomes a brand band once --section-wrapper-accent is set.
  • Light appearance by default. The tone tokens retone it for a dark page, but no dark mode is declared or selected automatically.

Example

Svelte
<script lang="ts">
	import Section from '$lib/components/section-wrapper-01/Section.svelte';
</script>

<Section id="plans" tone="inverse" labelledby="plans-title">
	<h2 id="plans-title" class="text-3xl font-semibold tracking-tight text-[var(--section-tone-ink)]">
		Starter is free for up to three people
	</h2>
	<p class="mt-4 max-w-xl text-lg text-[var(--section-tone-muted)]">
		Team is $12 per seat per month and Business is $24.
	</p>
</Section>

Building a page from sections#

Copy Section.svelte into src/lib/components/section-wrapper-01/. Each band of the page is one Section; put your heading, copy and components inside it, and hand the heading's id to labelledby for the sections a reader would navigate to.

Svelte
<script lang="ts">
	import Section from '$lib/components/section-wrapper-01/Section.svelte';
</script>

<Section id="timelines" labelledby="timelines-title">
	<h2 id="timelines-title">Every date on the roadmap, and what it waits on</h2>
	<p>Move a date and everything after it moves too.</p>
</Section>

<Section id="plans" tone="inverse" spacing="sm" labelledby="plans-title">
	<h2 id="plans-title">Starter is free for up to three people</h2>
</Section>

The section renders no heading of its own. It sets inherited defaults (text colour on every tone, color-scheme on the inverse and accent tones), but it never overrides a colour your markup or another component sets explicitly.

Presets#

Prop Values
spacing none 0 · sm 48 / 64 / 80 px · md (default) 64 / 96 / 128 px · lg 80 / 128 / 160 px, at phone, sm and lg widths
tone default (the page) · muted (one step off it) · inverse (a dark band) · accent (a band in your accent colour)
width prose max-w-2xl · default max-w-6xl · wide max-w-7xl · full the whole width, no gutters

The gutters (16, 24 and 32 px) sit on the section and the width caps the content inside them, so default gives 1152 px of content, the same shape as the catalogue's own sections. Sections on the same width share one content edge down the page; a different width centres on its own, so a prose section is a narrower centred column for long reading. On a phone they all start on the same 16 px edge. full hands the whole width to its content with no gutters, for content that brings its own: a catalogue section, or media that runs edge to edge.

Two sections on the same tone that are direct siblings read as one band: the second drops its top padding, so the gap between their content is the first section's bottom padding rather than both. Give neighbours different tones, or wrap one in its own element, to keep both paddings.

Content that follows the tone#

Each tone publishes four values for the content inside it:

Variable Default tone Inverse tone
--section-tone-ink #18181b #fafafa
--section-tone-muted #52525b #a1a1aa
--section-tone-hairline 8% black 12% of on-inverse
--section-tone-surface transparent #09090b

Use them in your own markup (text-[var(--section-tone-muted)], border-[var(--section-tone-hairline)]) and a heading or a divider follows the band. A catalogue section such as pricing-grid-01 already pads and constrains itself with the same padding and gutters as spacing="md" and width="default", so wrap it with spacing="none" and width="full"; it then lands on the same edge as the sections around it. It keeps its own tokens, including its surfaces, so on a dark band point every colour it paints at the tone, not only its text, or its white cards keep the dark band's pale ink:

Svelte
<Section tone="inverse" spacing="none" width="full">
	<div
		style="
			--pricing-grid-surface: var(--section-tone-surface);
			--pricing-grid-ink: var(--section-tone-ink);
			--pricing-grid-muted: var(--section-tone-muted);
			--pricing-grid-hairline: var(--section-tone-hairline);
			--pricing-grid-control-border: var(--section-tone-hairline);
			--pricing-grid-accent: var(--section-tone-ink);
			--pricing-grid-on-accent: var(--section-tone-surface);
		"
	>
		<PricingGrid title="Plans and pricing" {plans} />
	</div>
</Section>

The inverse and accent tones also set color and color-scheme, so inherited text and native controls are drawn for a dark fill. Links you add in those bands should carry an underline or another mark beyond colour.

Sticky headers#

Set the header's height once:

CSS
:root {
	--section-wrapper-header-offset: 4rem;
}

A jump to a section's id then stops 4rem down, so the band's top edge sits under the header. A section that follows a same-tone neighbour has no top padding, so it stops 2rem further down (the offset plus 2rem) to keep its heading off the header. Near the end of a short page the browser may not be able to scroll that far.

If you already set html { scroll-padding-top } for your header, leave this token at 0: the two add up. The collapsed section's extra 2rem applies either way.

The offset belongs to the section, so it only covers jumps to the section itself. A link or field inside it that receives focus scrolls by its own margin; if keyboard focus can land under your header, set html { scroll-padding-top } instead and leave the token at 0.

Retoning#

Variable Default Draws
--section-wrapper-ink #18181b Text on the default and muted tones
--section-wrapper-muted #52525b Secondary text on the default and muted tones
--section-wrapper-hairline rgb(0 0 0 / 0.08) Hairline value on the default and muted tones
--section-wrapper-surface transparent Default tone background
--section-wrapper-muted-surface #f4f4f5 Muted tone background
--section-wrapper-inverse-surface #09090b Inverse tone background
--section-wrapper-on-inverse #fafafa Text on the inverse tone
--section-wrapper-inverse-muted #a1a1aa Secondary text on the inverse tone
--section-wrapper-accent #18181b Accent tone background
--section-wrapper-on-accent #ffffff Text on the accent tone
--section-wrapper-inverse-scheme dark color-scheme on the inverse tone
--section-wrapper-accent-scheme dark color-scheme on the accent tone
--section-wrapper-header-offset 0px Anchor stop below a sticky header

In the neutral palette the accent is near-black, so the accent band sits close to the inverse one; set your brand colour and it becomes a brand band. A dark page swaps the ramps:

Svelte
<div
	class="bg-zinc-950"
	style="
		color-scheme: dark;
		--section-wrapper-surface: #09090b;
		--section-wrapper-muted-surface: #18181b;
		--section-wrapper-ink: #fafafa;
		--section-wrapper-muted: #a1a1aa;
		--section-wrapper-hairline: rgb(255 255 255 / 0.1);
		--section-wrapper-inverse-surface: #fafafa;
		--section-wrapper-on-inverse: #09090b;
		--section-wrapper-inverse-muted: #52525b;
		--section-wrapper-inverse-scheme: light;
	"
>
	<!-- sections -->
</div>

Re-check contrast after any change: 4.5:1 for text against each tone's surface.

Props and content inputs#

On this page
NameTypeRequiredDefaultDescription
childrenSnippetYesNoneThe section's content, rendered inside the inner container.
idstringNoNoneAnchor id for #links. The section stops below the sticky header set by --section-wrapper-header-offset.
spacing'none' | 'sm' | 'md' | 'lg'No'md'Vertical padding preset: none 0; sm 48/64/80 px; md 64/96/128 px; lg 80/128/160 px at phone, sm and lg breakpoints.
tone'default' | 'muted' | 'inverse' | 'accent'No'default'Background and text tone: the page, one step off the page, a dark band, or a band in the accent colour.
width'prose' | 'default' | 'wide' | 'full'No'default'Inner container: max-w-2xl, max-w-6xl or max-w-7xl of content inside shared 16/24/32 px gutters, or full: the whole width with no gutters, for content that brings its own (a catalogue section, edge-to-edge media).
labelledbystringNoNoneId of the heading inside that names the section. When set, the section is exposed as a named region; when omitted, no aria-labelledby is rendered.

Customization#

On this page

Pick a tone, spacing and width through props. Retone every tone through --section-wrapper-* variables, and read the four --section-tone-* values inside a section so your content follows the band it sits on.

  • Content colours: inside a section, use var(--section-tone-ink) for headings and body text, var(--section-tone-muted) for secondary text, var(--section-tone-hairline) for dividers and var(--section-tone-surface) for anything that must match the band. They change with the tone.
  • Accent: set --section-wrapper-accent and --section-wrapper-on-accent together; the accent tone fills the band with the first and sets text in the second. Keep them at 4.5:1. The accent band's muted text and hairline are mixed from the pair.
  • Page tones: --section-wrapper-ink, --section-wrapper-muted and --section-wrapper-hairline are the text and rule colours on the default and muted tones; --section-wrapper-surface is the default tone's background (transparent, so the page shows through) and --section-wrapper-muted-surface the muted band's.
  • Inverse: --section-wrapper-inverse-surface, --section-wrapper-on-inverse and --section-wrapper-inverse-muted set the dark band. Its hairline is mixed from on-inverse.
  • Native controls: --section-wrapper-inverse-scheme and --section-wrapper-accent-scheme set color-scheme on those bands (dark by default). Set the accent one to light if your accent is pale, so checkboxes and selects are drawn for a light fill.
  • Sticky header: set --section-wrapper-header-offset on :root to your header's height (4rem for a 64 px bar). Jumps to a section id then stop below it; a collapsed same-tone section stops 2rem further down.
  • Dark page retone: on the page wrapper set color-scheme: dark, then surface #09090b, muted-surface #18181b, ink #fafafa, muted #a1a1aa, hairline rgb(255 255 255 / 0.1), inverse-surface #fafafa, on-inverse #09090b, inverse-muted #52525b, inverse-scheme light (all --section-wrapper-*). usage.md has the snippet.
  • Spacing and widths: the padding and measure maps at the top of the script hold the Tailwind classes for each preset; edit them there to change the scale for every section at once.
  • Collapse: two direct-sibling sections on the same tone merge. To keep a gap between them, give them different tones or put the second in its own wrapper.
  • Catalogue sections: pricing-grid-01, faq-accordion-01 and similar already pad and constrain themselves with the same classes as spacing="md" and width="default". To put one on a tone, wrap it in a Section with spacing="none" and width="full"; it keeps its own padding and gutters and starts on the same edge as the default-width sections around it.

Public CSS variables

VariableToken
--section-wrapper-accentaccent
--section-wrapper-on-accentonAccent
--section-wrapper-inkink
--section-wrapper-mutedmuted
--section-wrapper-hairlinehairline
--section-wrapper-surfacesurface
--section-wrapper-muted-surfacemutedSurface
--section-wrapper-inverse-surfaceinverseSurface
--section-wrapper-on-inverseonInverse
--section-wrapper-inverse-mutedinverseMuted
--section-wrapper-inverse-schemeinverseScheme
--section-wrapper-accent-schemeaccentScheme
--section-wrapper-header-offsetheaderOffset

Accessibility#

On this page
  • The root is a section element. With labelledby it carries aria-labelledby and is exposed as a named region; without it no aria attribute is rendered, so it stays a generic container rather than an unnamed landmark.
  • Point labelledby at the section's own visible heading. Name only the sections a reader would navigate to; a landmark for every band makes the landmark list noisy.
  • Contrast (WCAG relative luminance): ink #18181b on white 17.7:1 and on the muted band #f4f4f5 16.1:1; muted #52525b 7.7:1 on white and 7.0:1 on the muted band; on the inverse band #fafafa measures 19.1:1 and #a1a1aa 7.8:1; white on the neutral accent 17.7:1 and on the blue accent 6.7:1.
  • Every tone sets the inherited text colour, and the inverse and accent tones also set color-scheme, so unstyled text, links that use currentColor and native controls are drawn for the band. Explicit colours on content inside are left alone.
  • With an id, scroll-margin-block-start takes the header offset token, so a jump to the section stops below a sticky header of that height. It does not cover focus moving to a control inside the section; html { scroll-padding-top } does.
  • Layout uses logical properties throughout and mirrors under dir="rtl".

Known limitations

  • Links inside a band are the consumer's markup: in the inverse and accent tones, make inline links distinguishable by more than colour (an underline) and give focus rings a colour that clears 3:1 against the band, such as var(--section-tone-ink).
  • Contrast figures cover the shipped palettes only; re-check any retoned token.

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.