Content
Separator
Divide groups of content with a hairline.
Designs the billing and onboarding flows, and reviews every new pricing page.
import { Separator } from "macbetter/separator"
export default function Example() {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 12, width: "100%", maxWidth: 360 }}>
<div>
<div style={{ fontWeight: 600 }}>Hollis Grant</div>
<div style={{ color: "var(--mb-label-secondary)" }}>hollis@macbetter.dev</div>
</div>
<Separator />
<p style={{ margin: 0 }}>Designs the billing and onboarding flows, and reviews every new pricing page.</p>
</div>
)
}When to use it
Use a separator between groups that a reader should take as separate, like a profile's name and its bio, or between links in a row. Apple's separators are thin and quiet: one physical pixel in a translucent gray, so they divide without drawing the eye.
Prefer space and grouping first. Inset grouped lists, cards and sections already separate content; add a line only when space alone doesn't.
Usage
import { Separator, SeparatorLabel } from "macbetter/separator"Vertical
Set orientation="vertical" between items in a row. It stretches to the row's height.
import { Separator } from "macbetter/separator"
const stats = [
{ value: "24", label: "Projects" },
{ value: "1,280", label: "Followers" },
{ value: "312", label: "Following" },
]
export default function Example() {
return (
<div style={{ display: "flex", gap: 20, alignItems: "center" }}>
{stats.map((stat, index) => (
<div key={stat.label} style={{ display: "contents" }}>
{index > 0 ? <Separator orientation="vertical" /> : null}
<div style={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
<span style={{ fontWeight: 600, fontVariantNumeric: "tabular-nums" }}>{stat.value}</span>
<span style={{ color: "var(--mb-label-secondary)" }}>{stat.label}</span>
</div>
</div>
))}
</div>
)
}Inset
inset="leading" starts the line at the row's text instead of its edge, as between the rows of an iOS list. inset alone insets both ends. Both use the platform's row padding; set --mb-separator-inset to line up with text that sits after an icon.
import { Separator } from "macbetter/separator"
const rows = [
{ title: "Plan", value: "Pro" },
{ title: "Seats", value: "8 of 10" },
{ title: "Renews", value: "December 1, 2026" },
]
export default function Example() {
return (
<div
style={{
width: "100%",
maxWidth: 420,
borderRadius: "var(--mb-group-radius)",
background: "var(--mb-bg-grouped-secondary)",
boxShadow: "0 0 0 0.5px var(--mb-separator)",
}}
>
{rows.map((row, index) => (
<div key={row.title}>
{index > 0 ? <Separator inset="leading" /> : null}
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
minHeight: "var(--mb-row-height)",
paddingInline: "var(--mb-row-padding)",
}}
>
<span>{row.title}</span>
<span style={{ color: "var(--mb-label-secondary)" }}>{row.value}</span>
</div>
</div>
))}
</div>
)
}With a label
SeparatorLabel puts a short word between two lines. The word carries the meaning, so the lines are hidden from screen readers.
import { Button } from "macbetter/button"
import { SeparatorLabel } from "macbetter/separator"
export default function Example() {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 14, width: "100%", maxWidth: 320 }}>
<Button variant="filled" block>
Sign In with Passkey
</Button>
<SeparatorLabel>or</SeparatorLabel>
<Button block>Email Me a Link</Button>
</div>
)
}Platform behavior
The line is the same hairline everywhere. Its inset follows the platform's row padding: 10 points on macOS and 16 on iOS and iPadOS, so an inset separator lines up with list text on each.
Accessibility
- A separator is announced with the
separatorrole and its orientation, so a screen reader hears the groups as separate. It never takes focus. - Pass
decorativewhen the surrounding content already shows the division; the separator getsrole="none". - In Increase Contrast, the separator token darkens on its own.
API
Separator
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" | Which way the line runs. |
inset | boolean | "leading" | — | `leading` insets the start by the row padding; `true` insets both ends. |
decorative | boolean | false | Hides the line from assistive technology. |
SeparatorLabel
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | The label, like "or". |
inset | boolean | — | Insets both ends by the row padding. |
Data attributes and variables
| Attribute or variable | On | Description |
|---|---|---|
data-orientation | .mb-separator | horizontal or vertical, from ui-sdk. |
data-inset | .mb-separator | both or leading. |
--mb-separator-color | .mb-separator | The line's color. Defaults to --mb-separator. |
--mb-separator-inset | .mb-separator | How far an inset line starts from the edge. |