Section divider
A divider between content areas: a hairline rule, a rule split by a visible label such as 'or' or 'Week 2', or a wave or slant that carries one section tone into the next without a seam.
cmp_section_divider_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_section_divider_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-00a30fdb0cccb5b2914125ba63d475e0d37486a1214192f9e44c66da125470ed
<script lang="ts" module>
export type SectionDividerVariant = 'rule' | 'label' | 'wave' | 'slant';
export type SectionDividerTone = 'default' | 'muted' | 'inverse';
export type SectionDividerSpacing = 'sm' | 'md' | 'lg';
</script>
<script lang="ts">
interface Props {
/** A hairline, a hairline split by a label, or a shape that carries one tone into the next. */
variant?: SectionDividerVariant;
/** Visible text for the label variant. Without it, the label variant draws a plain rule. */
label?: string;
/** Hide the divider from assistive tech; for a break that separates nothing in meaning. */
decorative?: boolean;
/** Wave and slant: the tone of the section above. */
fromTone?: SectionDividerTone;
/** Wave and slant: the tone of the section below. */
toTone?: SectionDividerTone;
/** Rule and label: the space above and below. Wave and slant: the depth of the shape. */
spacing?: SectionDividerSpacing;
}
let {
variant = 'rule',
label,
decorative = false,
fromTone = 'default',
toTone = 'muted',
spacing = 'md'
}: Props = $props();
const shape = $derived(variant === 'wave' || variant === 'slant');
const labelled = $derived(variant === 'label' && !!label?.trim());
/*
* A label with no spaces (an address, a file name) would wrap at a slash and leave a gap
* beside one rule; breaking anywhere lets its lines fill the box so both rules stay close.
*/
const unbroken = $derived(labelled && !/\s/.test(label!.trim()));
/*
* Rule and label sit in the flow of a page, so spacing is margin: 32 px for a break inside a
* column, 48 to 64 px between groups, 64 to 96 px between topics (DESIGN §3.3 proximity).
*/
const margin: Record<SectionDividerSpacing, string> = {
sm: 'my-8',
md: 'my-12 sm:my-16',
lg: 'my-16 sm:my-24'
};
/*
* A shape sits between two sections, which already bring their own padding, so spacing is
* the depth of the curve instead. It steps up with the width, so the angle stays low on a
* phone instead of a 360 px wedge.
*/
const depth: Record<SectionDividerSpacing, string> = {
sm: 'h-6 sm:h-8 lg:h-10',
md: 'h-8 sm:h-12 lg:h-16',
lg: 'h-12 sm:h-16 lg:h-24'
};
const fill: Record<SectionDividerTone, string> = {
default: 'fill-(--_surface)',
muted: 'fill-(--_muted-surface)',
inverse: 'fill-(--_inverse-surface)'
};
/*
* The upper region also strokes its own edge in its own tone, one screen pixel wide whatever
* the stretch, so where two solid tones meet it covers the pale anti-aliased seam. A
* transparent upper tone strokes nothing.
*/
const edge: Record<SectionDividerTone, string> = {
default: 'stroke-(--_surface)',
muted: 'stroke-(--_muted-surface)',
inverse: 'stroke-(--_inverse-surface)'
};
/*
* Drawn in a 1440 × 64 box and stretched to the element, so the curve spans any width. The
* two regions share one edge exactly, each painted from its own token, so either can be
* transparent and let the page through. The edge stays at least 4 units clear of the top and
* bottom (1.5 px at the shallowest depth), so the one-pixel overlap with each neighbour only
* ever paints that neighbour's own tone. The wave is one long, uneven swell rather than a
* repeating sine: the lower tone rises toward the end of the line, and the slant is that rise
* as a straight edge.
*/
const paths = {
wave: {
from: 'M0 0H1440V8C1300 0 1280 0 960 32C640 64 320 64 0 48Z',
to: 'M0 48C320 64 640 64 960 32C1280 0 1300 0 1440 8V64H0Z'
},
slant: {
from: 'M0 0H1440V4L0 60Z',
to: 'M0 60L1440 4V64H0Z'
}
};
const d = $derived(paths[variant === 'slant' ? 'slant' : 'wave']);
</script>
{#if shape}
<!--
-my-px tucks the shape one pixel under each neighbour, so a fractional section height never
opens a hairline crack. The SVG paints after both sections' backgrounds, and its edge rows
are their own tones, so the overlap is invisible.
-->
<div class={['section-divider -my-px w-full', depth[spacing]]} aria-hidden="true">
<svg
class="block size-full rtl:-scale-x-100"
viewBox="0 0 1440 64"
preserveAspectRatio="none"
focusable="false"
>
<path class={fill[toTone]} d={d.to} />
<path
class={[fill[fromTone], edge[fromTone]]}
d={d.from}
stroke-width="1"
vector-effect="non-scaling-stroke"
/>
</svg>
</div>
{:else if labelled}
<!--
The separator role makes its children presentational, so aria-label carries the text for
assistive tech while the same words stay visible. The rules keep at least 32 px each, and
the label wraps rather than squeezing them away. Pretty, not balanced, wrapping: a balanced
label keeps its box at full width while its lines shrink, leaving a gap beside each rule.
-->
<div
class={['section-divider flex items-center gap-4', margin[spacing]]}
role={decorative ? undefined : 'separator'}
aria-label={decorative ? undefined : label}
aria-hidden={decorative ? 'true' : undefined}
>
<span class="h-px min-w-8 flex-1 bg-(--_hairline)"></span>
<span
class={[
'max-w-lg min-w-0 text-center text-sm leading-5 font-medium text-pretty text-(--_muted)',
unbroken ? 'break-all' : 'break-words'
]}
>
{label}
</span>
<span class="h-px min-w-8 flex-1 bg-(--_hairline)"></span>
</div>
{:else}
<hr
class={['section-divider border-0 border-t border-(--_hairline)', margin[spacing]]}
aria-hidden={decorative ? 'true' : undefined}
/>
{/if}
<style>
/*
* Public tokens: set --section-divider-* on the divider or any ancestor. The three surfaces
* are the tones a shape joins; point them at your section colours (for section-wrapper-01,
* --section-divider-muted-surface: var(--section-wrapper-muted-surface) and so on).
*/
.section-divider {
--_muted: var(--section-divider-muted, #52525b);
--_hairline: var(--section-divider-hairline, rgb(0 0 0 / 0.1));
--_surface: var(--section-divider-surface, transparent);
--_muted-surface: var(--section-divider-muted-surface, #f4f4f5);
--_inverse-surface: var(--section-divider-inverse-surface, #09090b);
}
</style>
Usage#
On this pagePresentational only. It draws a rule, a labelled rule or a tone-to-tone shape and nothing else: it paints no section backgrounds, so the sections on either side of a wave or slant keep their own, and their colours have to match the divider's surface tokens. It renders no heading; a label is separator text, not a title.
- Suggested location
src/lib/components/section-divider-01
Limitations
- A wave or slant only looks seamless when the sections beside it use the same colours as its surface tokens; set
--section-divider-surface, -muted-surface and -inverse-surface to your section backgrounds. - Shapes overlap each neighbour by one pixel to hide sub-pixel cracks, and keep their edge clear of that pixel. Put them directly between two full-width sections; inside padding or a gap they show as a band of their own.
- A translucent surface token blends with the page and with the one-pixel stroke that hides the seam between two solid tones; use opaque colours or transparent.
- There is one wave and one slant, both rising toward the end of the line. To run one the other way, wrap it in an element with a horizontal flip.
- A label is read as the separator's name, not as a heading. For a real chapter title that should appear in a heading list, use a heading.
- The label variant without label text draws a plain rule.
- Light appearance only. Retone the tokens for a dark page; no dark mode is declared.
Example
<script lang="ts">
import SectionDivider from '$lib/components/section-divider-01/SectionDivider.svelte';
</script>
<section class="px-4 py-16">
<h2 class="text-3xl font-semibold tracking-tight">See every launch on one line</h2>
</section>
<SectionDivider variant="wave" fromTone="default" toTone="muted" />
<section class="bg-zinc-100 px-4 py-16">
<h2 class="text-3xl font-semibold tracking-tight">Starter is free for up to three people</h2>
<SectionDivider variant="label" label="or" spacing="sm" />
</section>Section divider#
Four ways to separate content, all from one component:
| Variant | Renders | Exposed as |
|---|---|---|
rule |
a one-pixel hairline (hr) |
separator, or hidden |
label |
a hairline split by visible text | separator named by the text |
wave |
one long, uneven swell from one tone to another | always hidden |
slant |
a straight edge from one tone to another | always hidden |
Rules and labels#
Use a rule or label where the content changes topic: two releases in a changelog, the chapters of
a course, "or" between two ways to sign in. Between things that space already separates, you
probably want no divider at all; where you want one for rhythm only, pass decorative.
<SectionDivider />
<SectionDivider variant="label" label="or" spacing="sm" />
<SectionDivider variant="label" label="Week 2 · Shaping and proofing" />A long label wraps, up to about 75 characters a line, with its lines filling its box. Each rule keeps at least 32 px, so the label never squeezes them away on a phone.
Shapes between sections#
Put a wave or slant directly between two full-width sections. It paints no section of its own:
the upper region is the fromTone surface and the lower region the toTone surface, and it
tucks one pixel under each neighbour so no sub-pixel crack opens between them.
<section class="py-24">…</section>
<SectionDivider variant="wave" fromTone="default" toTone="muted" />
<section class="bg-zinc-100 py-24">…</section>The shape only looks seamless when its surfaces match your sections. The defaults are
transparent (the page), #f4f4f5 (bg-zinc-100) and #09090b (bg-zinc-950). Set them once,
on a page wrapper:
.page {
--section-divider-surface: #fffdf8;
--section-divider-muted-surface: #f5efe6;
--section-divider-inverse-surface: #1c1917;
}With section-wrapper-01, point the divider at the wrapper's tokens so one retone moves both:
.page {
--section-divider-muted-surface: var(--section-wrapper-muted-surface, #f4f4f5);
--section-divider-inverse-surface: var(--section-wrapper-inverse-surface, #09090b);
}spacing sets the shape's depth rather than a margin: the sections either side already have
their padding. The depth steps up with the viewport so the angle stays low on a phone.
Right to left#
Shapes mirror under dir="rtl", so the lower tone still rises toward the end of the line.
Props and content inputs#
On this page| Name | Type | Required | Default | Description |
|---|---|---|---|---|
variant | 'rule' | 'label' | 'wave' | 'slant' | No | 'rule' | A hairline rule, a rule split by a label, or a wave or slant shape that joins two section tones. |
label | string | No | None | Visible text for the label variant, also used as the separator's accessible name. Wraps at about 75 characters a line. Without it the label variant draws a plain rule. |
decorative | boolean | No | false | Hide the rule or label from assistive tech, for a break that separates nothing in meaning. Wave and slant are always hidden. |
fromTone | 'default' | 'muted' | 'inverse' | No | 'default' | Wave and slant: the tone of the section above, painted from the matching surface token. |
toTone | 'default' | 'muted' | 'inverse' | No | 'muted' | Wave and slant: the tone of the section below. |
spacing | 'sm' | 'md' | 'lg' | No | 'md' | Rule and label: margin above and below (sm 32 px; md 48/64 px; lg 64/96 px). Wave and slant: the shape's depth (sm 24/32/40 px; md 32/48/64 px; lg 48/64/96 px at phone, sm and lg breakpoints). |
Customization#
On this pagePick a variant, tones and spacing through props. Retone the label, rule and the three shape surfaces through five --section-divider-* variables.
- Section colours: set
--section-divider-surface,--section-divider-muted-surfaceand--section-divider-inverse-surfaceto the backgrounds of your default, muted and dark sections. surface is transparent by default so a wave from the page tone shows whatever the page is. - With
section-wrapper-01: on a shared ancestor set--section-divider-muted-surface: var(--section-wrapper-muted-surface) and--section-divider-inverse-surface: var(--section-wrapper-inverse-surface), so retoning the sections retones the shapes. - Rule and label on a dark band: set
--section-divider-hairlineto rgb(255 255 255 / 0.12) and--section-divider-mutedto#a1a1aaon that band. - Cream or tinted page: set
--section-divider-muted-surfaceto the tinted band (for example#f5efe6) and--section-divider-hairlineto rgb(68 40 0 / 0.12), so the rule takes the page's warmth. - Shape geometry: the paths object at the top of the script holds the wave and slant in a 1440 by 64 box. Edit the curve there; keep the lower region one unit higher than the upper one so solid tones never leave a seam.
- Spacing: the margin and depth maps hold the Tailwind classes for each preset.
Public CSS variables
| Variable | Token |
|---|---|
--section-divider-muted | muted |
--section-divider-hairline | hairline |
--section-divider-surface | surface |
--section-divider-muted-surface | mutedSurface |
--section-divider-inverse-surface | inverseSurface |
Accessibility#
On this page- The rule is an hr, exposed as a separator. With decorative it carries
aria-hidden. - The label variant is a div with role separator and
aria-labelset to the label; the same text stays visible. The separator role makes its children presentational, so its accessible name is the label alone; how often a screen reader speaks it depends on the reader. - Wave and slant are always
aria-hidden, with focusable false on the SVG: a change of background tone carries no meaning. - Use a separator only where the content actually changes topic. Between items that space already separates, pass decorative.
- Label contrast:
#52525bon white 7.7:1 and on#f4f4f57.0:1. The hairline is decorative and needs no contrast ratio. - Layout uses logical properties; shapes mirror under dir="rtl".
Known limitations
- Screen reader support for a named separator varies: some announce the label with the separator, some announce only the separator. If the label carries information a reader must not miss, put it in the content as well.
- Contrast figures cover the default tokens only; re-check retoned ones.
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 · 1 October 2026
Only the current release is available. Keep downloaded source and its receipt if you need to use it again later.