Separator
A hairline divider. A Separator draws a neutral border line between regions or items — horizontal across the flow, vertical between items in a row — and Base UI gives it the separator role so the division is announced without any extra wiring.
Orientation
orientation picks the axis: horizontal (the default) draws a full-width rule across the inline flow, while vertical draws a full-height line that divides items in a row. A vertical separator stretches to its row's height on its own — no need to give the row an explicit height.
"use client";
import { Separator } from "@stridge/noctis";
export default function Example() {
return (
<div className="flex w-full max-w-sm flex-col gap-4 text-regular text-muted">
<span>Profile</span>
<Separator />
<span>Workspace</span>
{/* The row sets no height — a vertical separator stretches to fill it on its own. */}
<div className="flex items-center gap-3 text-mini text-subtle">
<span>Edit</span>
<Separator orientation="vertical" />
<span>Share</span>
<Separator orientation="vertical" />
<span>Delete</span>
</div>
</div>
);
}
Decorative vs semantic
By default a Separator is semantic — it carries the separator role, so a screen reader announces the division. When the line is purely visual and the regions it splits already read as distinct — the groups in a toolbar, sections that each carry their own landmark — set decorative to drop it to role="none" so assistive tech skips it. The paint is identical; only the role changes.
"use client";
import { Button, Separator } from "@stridge/noctis";
import { AlignCenter, AlignLeft, AlignRight, Bold, Image, Italic, Link, Underline } from "lucide-react";
export default function Example() {
return (
// A toolbar already groups its controls, so the rules between clusters are decorative — screen
// readers skip them instead of announcing a separator before every group.
<div
className="flex items-center gap-1 rounded-md border border-border bg-background p-1"
role="toolbar"
aria-label="Formatting"
>
<Button variant="ghost" iconOnly aria-label="Bold" startIcon={Bold} />
<Button variant="ghost" iconOnly aria-label="Italic" startIcon={Italic} />
<Button variant="ghost" iconOnly aria-label="Underline" startIcon={Underline} />
<Separator decorative orientation="vertical" />
<Button variant="ghost" iconOnly aria-label="Align left" startIcon={AlignLeft} />
<Button variant="ghost" iconOnly aria-label="Align center" startIcon={AlignCenter} />
<Button variant="ghost" iconOnly aria-label="Align right" startIcon={AlignRight} />
<Separator decorative orientation="vertical" />
<Button variant="ghost" iconOnly aria-label="Insert link" startIcon={Link} />
<Button variant="ghost" iconOnly aria-label="Insert image" startIcon={Image} />
</div>
);
}
In a list
A separator is most at home between the peer rows of a list or settings panel. The line spans its container edge to edge by default; pull it off both ends — so it stops short of a row's padding — by setting the --noctis-separator-margin seam. It applies as a logical margin, so the inset mirrors under RTL.
"use client";
import { Separator } from "@stridge/noctis";
import type { CSSProperties } from "react";
const rows = [
{ label: "Notifications", value: "All activity" },
{ label: "Privacy", value: "Only me" },
{ label: "Language", value: "English" },
];
export default function Example() {
return (
<div className="w-full max-w-sm overflow-hidden rounded-lg border border-border bg-background">
{rows.map((row, index) => (
<div key={row.label}>
{/* A rule between peer rows — inset off the row padding via the margin seam so the
lines stop short of the panel edges. */}
{index > 0 && <Separator style={{ "--noctis-separator-margin": "0.75rem" } as CSSProperties} />}
<div className="flex items-center justify-between px-3 py-2.5 text-regular">
<span className="text-muted">{row.label}</span>
<span className="text-subtle">{row.value}</span>
</div>
</div>
))}
</div>
);
}
In a row of metadata
Vertical separators thread through an inline run of metadata — a byline, a breadcrumb of facts — keeping the items distinct without bullets. Here the rules are decorative (the text items are already separate to a screen reader) and each stretches to the line's height automatically.
"use client";
import { Separator } from "@stridge/noctis";
export default function Example() {
return (
// An inline byline: the rules between metadata items are decorative, and each stretches to the
// text line's height without any explicit sizing.
<div className="flex items-center gap-2.5 text-mini text-subtle">
<span>Jane Doe</span>
<Separator decorative orientation="vertical" />
<span>5 min read</span>
<Separator decorative orientation="vertical" />
<span>Edited 2h ago</span>
</div>
);
}
On a surface
The hairline fills from the neutral border role, which the engine re-points inside every elevation scope. So a Separator dropped into a Surface — or any data-elevation subtree — re-tones to read correctly against that surface, with no props: lighter as the surface lifts, settling back in a sunken well.
"use client";
import { Separator, Surface } from "@stridge/noctis";
const scopes = [
{ elevation: undefined, label: "Base" },
{ elevation: "elevated", label: "Elevated" },
{ elevation: "menu", label: "Menu" },
{ elevation: "sunken", label: "Sunken" },
] as const;
export default function Example() {
return (
<div className="grid w-full max-w-md grid-cols-2 gap-3">
{scopes.map((scope) => (
<Surface key={scope.label} elevation={scope.elevation} bordered className="rounded-lg p-3 text-mini text-muted">
<span>{scope.label}</span>
{/* The same Separator — no props — re-tones to the surface's elevation scope. */}
<Separator className="my-2" />
<span className="text-subtle">Settings</span>
</Surface>
))}
</div>
);
}
Accessibility
A separator is inert: it is not focusable and has no keyboard interaction — it conveys structure, not behaviour. A semantic separator is announced with its orientation; a decorative one is skipped entirely.
| Key | Action |
|---|---|
| Tab | Skips the separator — it is never a focus stop. |
A draggable divider that resizes the regions on either side is a different control (the window splitter pattern, with its own focus and value semantics), not this component.
Anatomy
Separator is a single element — no compound subparts. It renders a <div> with the separator role (or role="none" when decorative); the hairline is a filled box (a background-color plus a 1px cross-axis size), so the same element draws both orientations.
Separator— the divider line. Props:orientation(horizontaldefault, orvertical) anddecorative(falsedefault), plus the native attributes of the rendered element.Separator.props(...)— the D12 escape hatch: a spreadabledata-slot+data-orientationbag for styling a foreign element as a separator in place.
The rendered element carries data-slot="noctis-separator" for styling and testing, and data-orientation (which Base UI stamps) that the cross-axis size keys off.
Design tokens
Generated from the component's declaration — the same graph that mints the CSS, so a variable name or its resolution default can't drift. Retune the line by overriding the --noctis-separator-* seam on an ancestor — e.g. .muted { --noctis-separator-background-color: var(--noctis-color-faint); } lightens every divider in that region. See Customization for the full override ladder and Tokens for the whole graph.
API reference
Generated from the component's types — every prop, type, default, and description comes straight from the source; the separator also passes through the native attributes of its rendered element. Expand a row for the full type and description.