Divider
A thin line that separates two groups of content, with an optional centred label, drawn as a neutral hairline and carrying no status or category colour.
Preview
divider · base base · style base-lyraOpen under the product themeInstallation
pnpm dlx shadcn@latest add @opsinjs/divider
The @opsinjs namespace is declared in your components.json. Everything it installs is code you then own. There is no runtime package to keep in step.
Usage
import { Divider } from "@/components/ui/divider"<Divider />
<Divider label="Earlier" />
<Divider orientation="vertical" />When to use it
Use it when
- Between two stacked groups that are already distinct, to make the seam visible without giving either group a surface of its own.
- As a named boundary in a list, with a short label such as "Earlier" or "Today", echoing a grouping the list structure already carries.
- As a thin vertical rule between a few inline facts, inside a flex row that gives the rule its height.
Do not use it when
- You are grouping content that needs its own background, edge or padding, rather than a line between peers.Use
cardinstead. - You are separating layers of the interface by depth, such as a panel above the page.Use
surfaceinstead. - You are offering a choice between mutually exclusive views and the lines are really the seams between options.Use
segmented-controlinstead.
Anatomy
DividerThe root. A neutral hairline on neither colour axis. Unlabelled, it is one element with role separator.Controlled byorientationDivider labelOptional centred text on a horizontal rule, in muted ink. Its presence drops the separator role.Controlled bylabel
Examples
A labelled boundary in a list
label marks the seam between today's items and the earlier ones. The two lists
carry the grouping on their own; the divider is the visible echo, not the only signal.
divider-a-labelled-group-boundary · base base · style base-lyraOpen under the product themeA vertical rule between inline facts
orientation="vertical" draws a hairline that takes its height from the flex row
around it. In a block that gives it no height it collapses to nothing.
divider-vertical-between-inline-facts · base base · style base-lyraOpen under the product themeContent guidelines
A label is a short plain noun for the boundary it marks: Earlier, Today, Archived. It is not a heading and not a sentence, so no verb and no full stop. Never let a divider be the only thing separating two groups; group with a list, a heading or a region first, then add the line.
Do
A divider between two lists that already carry their own structure, so the line is an echo.
Don’t
A divider between two runs of paragraphs to imply sections a screen reader cannot hear.
Accessibility
Checked by pnpm run check:a11y on every commit. Every colour is a role
token, no type size is in px, and no banned word appears anywhere.
Audited against WCAG 2.2 AA, in a source pass over divider.tsx and its two
examples and a rendered pass over the shipped preview. The audit is author run,
not an independent review, and clinical review is still pending. It found
nothing to change.
- Not focusable and captures no key, in either orientation and whether or not it is labelled.
- Unlabelled, the root exposes
role="separator"witharia-orientation, so a reader navigating by region can skip it. - Labelled, the root has no role, because a separator cannot carry an accessible
name. The label is plain text between two
aria-hiddenhairlines. - A divider is never the only carrier of grouping for a screen reader; the groups either side must be separated by structure too.
Residual, not yet measured. The hairline's contrast against a card, a sheet
and the page, and the labelled form's footnote text against
--muted-foreground, are both shared role token pairs missing from the
generated contrast table; fixing either means editing the token set, which sits
outside this component's own file.
| Key | Action | Notes |
|---|---|---|
| Tab | Skips the divider | Not focusable and not a tab stop, in either orientation, labelled or not. |
| Shift+Tab | Skips the divider in reverse | Focus moves straight to the interactive element before it, in visual order. |
| Pair | Theme | APCA Lc | WCAG 2.2 | Floor |
|---|---|---|---|---|
| body text on the page | light | 101.6 | 17.18:1 | Pass |
| body text on the page | dark | -100.5 | 18.00:1 | Pass |
| body text on a card | light | 104.7 | 17.96:1 | Pass |
| body text on a card | dark | -99.6 | 16.32:1 | Pass |
| body text on the muted ground | light | 98.1 | 16.32:1 | Pass |
| body text on the muted ground | dark | -97.5 | 13.77:1 | Pass |
| secondary text on the page | light | 83.9 | 7.42:1 | Pass |
| secondary text on the page | dark | -80.7 | 13.44:1 | Pass |
| secondary text on a card | light | 87.0 | 7.76:1 | Pass |
| secondary text on a card | dark | -79.8 | 12.19:1 | Pass |
| secondary text on the muted ground | light | 80.4 | 7.05:1 | Pass |
| secondary text on the muted ground | dark | -77.8 | 10.28:1 | Pass |
| a hairline boundary on the page | light | 19.3 | 1.41:1 | Below floor |
| a hairline boundary on the page | dark | -8.2 | 1.90:1 | Below floor |
| a hairline boundary on a card | light | 22.4 | 1.47:1 | Below floor |
| a hairline boundary on a card | dark | -7.3 | 1.72:1 | Below floor |
| a hairline boundary on the muted ground | light | 15.8 | 1.34:1 | Below floor |
| a hairline boundary on the muted ground | dark | 0.0 | 1.45:1 | Below floor |
| a placeholder boundary on the page | light | 69.8 | 4.52:1 | Pass |
| a placeholder boundary on the page | dark | -51.0 | 7.62:1 | Pass |
| a placeholder boundary on a card | light | 72.8 | 4.72:1 | Pass |
| a placeholder boundary on a card | dark | -50.1 | 6.91:1 | Pass |
| a placeholder boundary on the muted ground | light | 66.2 | 4.29:1 | Pass |
| a placeholder boundary on the muted ground | dark | -48.1 | 5.83:1 | Pass |
| a placeholder fill on the page | light | 8.3 | 1.18:1 | Below floor |
| a placeholder fill on the page | dark | -8.2 | 1.90:1 | Below floor |
| a placeholder fill on a card | light | 11.4 | 1.23:1 | Below floor |
| a placeholder fill on a card | dark | -7.3 | 1.72:1 | Below floor |
| a placeholder fill on the muted ground | light | 0.0 | 1.12:1 | Below floor |
| a placeholder fill on the muted ground | dark | 0.0 | 1.45:1 | Below floor |
| a placeholder boundary on its own fill | light | 58.9 | 3.84:1 | Pass |
| a placeholder boundary on its own fill | dark | -41.5 | 4.01:1 | Below floor |
| the card hairline on the page | light | 69.8 | 4.52:1 | Pass |
| the card hairline on the page | dark | -28.7 | 4.19:1 | Below floor |
| the card hairline on a card | light | 72.8 | 4.72:1 | Pass |
| the card hairline on a card | dark | -27.9 | 3.80:1 | Below floor |
These are the measured token pairs this component draws colour from, not a measurement of the component itself.
API reference
Prop
Type
Generated from DividerProps in registry/bases/base/divider.tsx.
orientation defaults to horizontal; an unknown value is drawn horizontal with
a development warning. label applies to the horizontal form only and is dropped
with a warning on a vertical rule. A whitespace-only label counts as none.
className is the only route by which colour can reach the component, and both
colour axes stay off it.
Related
- Card groups content by giving it a background, an edge and padding. A divider only draws a line.
- Surface places a group on its own material at a chosen depth. A divider marks a seam between peers.
- SegmentedControl offers a choice between views. Its seams look like dividers but carry a selection.