Skip to content

Divider

A hairline that separates — and the far more common case where spacing already did the job and a line is just noise.

Also called Separator, Rule, HR — in this system all of them are Divider.

Live preview

Everything below is the real component. Change the controls, tab through it, and turn on Inspector Mode to read any value off the screen.

Playground

Deployment 4021


Deployment 4019

Try spacing first

The same two groups, separated by a line and by air. The gap groups just as clearly and adds nothing to look at.

Spacing
NotificationsEmail, Slack
Security2FA, sessions
DividerSame grouping, more ink
NotificationsEmail, Slack

Security2FA, sessions

Vertical dividers

Between metadata on one line and between groups in a toolbar. Always shorter than the row, so they separate rather than enclose.

eu-west-242 secondsAda Lovelace

Labelled dividers

An "or" between two alternative paths. It is the one case where the divider is genuinely carrying meaning rather than just separating.

Inset dividers in a list

In a list with leading avatars, the divider starts at the text rather than the container edge — it separates the rows, not the whole panel.

AAda Lovelace

GGrace Hopper

AAlan Turing

Interaction states

Every state a user can put this component into, rendered side by side. If a state is missing here, it is missing in production too.


Horizontal
Vertical
Labelled

Inset
In a toolbar
42seu-west-2
Between metadata
Body
Footer
Card footer

Anatomy

Every part, every measurement, and the reason it is that number.

Content above

Content below

A one-pixel rule with more space above than below, and an optional label that interrupts the line rather than sitting on it.

  1. Thickness1px, never more

    A 2px divider is a border, and a border implies a container. One pixel is enough at every density — the contrast does the work, not the weight.

  2. Colour--ds-border-subtle

    The lightest border token. A divider drawn in the default border colour competes with the edges of the components around it.

  3. Margin above20px

    More than below. The eye reads a rule as belonging to the content beneath it, so equal margins make it float between two blocks belonging to neither.

  4. Margin below16px

    The asymmetry is the whole rule. Roughly a 5:4 ratio is enough to feel deliberate without looking like a mistake.

  5. Label12px, muted, centred

    The line breaks around the label rather than running behind it. A label sitting on top of an unbroken rule reads as struck through.

  6. InsetAligned to the content

    In a list with leading avatars the rule starts at the text, so it separates the rows rather than cutting the whole panel in half.

  7. Vertical heightShorter than its row

    16–20px in a 44px toolbar. A full-height vertical rule reads as a container edge and the toolbar looks like two panels.

Design tokens used

Values are read live from the running stylesheet, so this table can never drift from the code. Click any value to copy it.

Color

TokenValueUsed for
--ds-border-subtle—The rule itself
--ds-fg-muted—The optional label

Spacing

TokenValueUsed for
--space-5Margin above
--space-4Margin below — deliberately less
--space-2Vertical divider inline margins
label gapBreak in the rule either side of a label

Radius

TokenValueUsed for
thicknessEvery divider, at every density — 2px is a border

Typography

TokenValueUsed for
--text-captionLabel

Recommended sizes

Pick a size from this table. Do not invent a new one — a fourth height is how a design system starts dying.

SizeHeightLabel gapWhen to use
Horizontal1px20px above, 16px belowBetween sections of equal weight. The asymmetry is not optional.
Vertical16–20px8px inlineIn a toolbar or a metadata row. Always shorter than the row it sits in.
Inset—Aligned to contentIn a list with leading content, starting at the text rather than the container edge.
Labelled1px12px either side of the labelThe line breaks around the label, never runs behind it.
Card footer—0 — the padding provides itFull-bleed to the card edge, with the footer’s own padding doing the spacing.

Do

NotificationsSecurity
Increase the gap before adding a lineProximity groups without adding anything to look at. If more space makes the grouping clear, the divider was never needed.
20px above
16px below
Give it more space above than belowThe eye reads a rule as belonging to the content beneath it. Equal margins make it float between two blocks, belonging to neither.
<hr aria-hidden="true" />
role="separator" only when it groups
Hide it from assistive tech when it is decorativeMost dividers convey nothing that spacing does not. Announcing "separator" a dozen times down a page is noise.
OneTwo
Keep vertical dividers shorter than the rowA full-height rule reads as a container edge, and a toolbar with one looks like two panels pushed together.

Don't

Security


Do not add one under a headingThe heading is already the boundary. The line duplicates it, and the pair reads as a document from 2004.
Card
Card
Do not divide items that already have edgesTwo card borders plus a divider is three lines in the same 20px. The gap between the cards is the separation.
Do not use one thicker than 1pxAt 2px it is a border, and a border implies a container the user then looks for. If it needs emphasis, use space instead.


Do not stack dividers with paddingA rule at the bottom of a padded section and another at the top of the next produces a double line with a gap — the classic sign of two components not talking to each other.

Accessibility

Not a checklist to run at the end. These are the requirements the component was built from.

1.3.1Info and RelationshipsA1.4.11Non-text ContrastAA

Contrast

  • A decorative divider has no contrast requirement — it is aria-hidden and carries nothing.
  • A divider that is the only grouping signal is meaningful non-text content and owes 3:1 under 1.4.11.
  • In forced-colors mode, a divider drawn as a background disappears. Use a border or set forced-color-adjust so the boundary survives.
  • A labelled divider’s text is content and owes 4.5:1.

Keyboard

—A divider is never focusable and never interactive.
TabPasses straight over it. A separator with a tabindex is always a bug.

Screen readers

  • A decorative divider should be silent. Twelve "separator" announcements down a settings page is noise that obscures the content.
  • Where a divider genuinely groups, the group should also carry a label — role="group" with aria-label conveys far more than a bare separator.
  • A labelled divider announces its label. "or" between two sign-in options is meaningful and should be heard.

Focus & touch

  • A divider is never in the focus order. If it needs to be — as a resize handle, for instance — it is a different component with role="separator", tabindex and aria-valuenow, not a divider with extra behaviour.
  • Dividers are not targets and need no touch consideration of their own — but they do compete for vertical space. On a phone, prefer spacing: a settings page with eight rules is eight pixels of ink and eight interruptions in a column that is already narrow.
AttributeApplied toNotes
aria-hidden="true"A decorative dividerThe default. Most dividers convey nothing that spacing does not already convey.
role="separator"A grouping dividerOnly where the line is the sole grouping signal — between menu groups, for example.
aria-orientation="vertical"A vertical separatorHorizontal is the default and does not need stating.
<hr>A thematic break in proseThe native element already has role="separator". Reach for it before a styled div.

Code

Example usage

tsx
1import { Divider } from '@/ui/Display'23// Decorative by default: aria-hidden, no role.4<Divider />56// Vertical, in a toolbar. Always shorter than the row.7<Divider orientation="vertical" className="h-5" />89// Labelled — the one case where the divider carries meaning.10<Divider label="or" />1112// Meaningful: between groups in a menu, where the line is the ONLY grouping13// signal a sighted user gets.14<div role="menu">15  <button role="menuitem">Rename</button>16  <div role="separator" />17  <button role="menuitem">Delete</button>18</div>1920// The asymmetry, as a token pair. The eye reads a rule as belonging to the21// content BENEATH it.22.section-break {23  margin-block: var(--space-5) var(--space-4);   /* 20px / 16px */24}2526// Before reaching for this component at all, try:27<Stack gap="lg">…</Stack>

Framework-free HTML

html
<!-- Decorative. Silent to assistive tech. -->
<hr aria-hidden="true" class="ds-divider" />

<!-- Thematic break in prose. <hr> already has role="separator". -->
<p>The rollback completed in eight seconds.</p>
<hr />
<p>A postmortem was filed the following morning.</p>

<!-- Meaningful: the line is the only grouping signal here. -->
<div role="menu" aria-label="Actions">
  <button type="button" role="menuitem">Rename</button>
  <button type="button" role="menuitem">Duplicate</button>
  <div role="separator"></div>
  <button type="button" role="menuitem">Delete</button>
</div>

<!-- Vertical, in a toolbar. -->
<div role="toolbar" aria-label="Formatting">
  <button type="button">…</button>
  <span role="separator" aria-orientation="vertical"></span>
  <button type="button">…</button>
</div>

<!-- Labelled: the rule breaks around the word rather than running behind it. -->
<div class="ds-divider ds-divider--labelled">
  <span>or</span>
</div>

CSS

css
.ds-divider {
  border: 0;
  block-size: 1px;                   /* never 2: that is a border */
  background: var(--ds-border-subtle);
  /* More above than below. The eye reads a rule as belonging to the content
     beneath it, so equal margins leave it belonging to neither. */
  margin-block: var(--space-5) var(--space-4);
}

.ds-divider[aria-orientation='vertical'],
.ds-divider--vertical {
  inline-size: 1px;
  /* Shorter than the row: a full-height rule reads as a container edge. */
  block-size: 20px;
  margin-block: 0;
  margin-inline: var(--space-2);
}

/* The line breaks AROUND the label. A label on an unbroken rule reads as
   struck through. */
.ds-divider--labelled {
  display: flex;
  align-items: center;
  gap: 12px;
  background: none;
  block-size: auto;
  color: var(--ds-fg-muted);
  font-size: 12px;
}
.ds-divider--labelled::before,
.ds-divider--labelled::after {
  content: '';
  flex: 1;
  block-size: 1px;
  background: var(--ds-border-subtle);
}

/* In a list with leading avatars, separate the ROWS, not the panel. */
.ds-list > * + *::before {
  content: '';
  display: block;
  block-size: 1px;
  margin-inline-start: 52px;         /* aligned to the text */
  background: var(--ds-border-subtle);
}

/* A background-drawn rule vanishes in forced colors. */
@media (forced-colors: active) {
  .ds-divider { border-block-start: 1px solid; block-size: 0; }
}

Component API

Divider

PropTypeDefaultDescription
orientation'horizontal' | 'vertical''horizontal'Vertical dividers are always shorter than the row they sit in.
labelReactNode—Breaks the rule around a centred label. The one case where a divider carries meaning.
decorativebooleantruearia-hidden by default. Set false only where the line is the sole grouping signal.
classNamestring—Where the inset and the vertical height are applied — both are context, not variants.

Notes

Professional tips

  • Count the dividers on a screen. More than three or four usually means the layout is relying on lines where it should be relying on space.
  • In a list, put the divider on the item rather than between items — a first-child or last-child rule is easier to reason about than a separate element per gap.
  • Full-bleed dividers in a card should extend to the card edge, ignoring its padding. A rule that stops short of the border looks like a rendering error.
  • For metadata rows, a vertical divider reads more cleanly than a bullet, which users mistake for list content.
  • Do not animate a divider. It is a boundary, not an event, and a line that fades in draws attention it does not deserve.

Performance

  • Prefer a border or a pseudo-element over an extra DOM node in long lists. A thousand-row list with a divider element per row is a thousand nodes for a thousand pixels.
  • Use a single background-image with a linear-gradient for repeated dividers in a virtualised list rather than an element per gap.
  • A 1px background can render at 0.5px or 1.5px on fractional device pixel ratios. Use a border where crispness matters, or accept the softness — it is usually invisible.

Common mistakes

  • Reaching for a divider when the real fix was more spacing.
  • Equal margins above and below, so the rule belongs to neither block.
  • A rule under every heading, duplicating a boundary the heading already made.
  • Full-height vertical dividers, which read as container edges.
  • role="separator" on decorative dividers, producing a dozen pointless announcements.
  • A 2px divider, which is a border and implies a container.
  • Background-drawn rules that vanish in forced-colors mode.
  • A divider between items that already have their own borders.

Real-world recommendations

  • Dividers proliferate during design reviews because they are the easiest thing to ask for when something "feels cramped". Audit them a week later and most can go.
  • In dense data UIs — tables, logs, terminals — dividers earn their place, because the spacing budget genuinely does not exist.
  • Menus are the strongest case: separators between groups measurably reduce mis-selection, and they are one of the few places the line carries real information.
  • If a page looks better with the dividers removed, remove them. That test takes ten seconds and is right more often than not.