---
title: "Divider"
description: "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."
url: "https://opsinjs.pensievelabs.org/components/divider"
source: "https://opsinjs.pensievelabs.org/components/divider.md"
section: "Components"
status: "shipped"
kind: "component"
category: "layout"
reviewed: "2026-09-20"
reviewer: "engineering"
aliases: ["separator", "rule"]
usedIn: ["diabetes-medicines-app"]
implemented: true
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> `<StubNotice>` IS THE EXCEPTION, AND IT IS THE ONE TO READ. It is a
> paired element rather than a self-closing one, and the text between
> its opening and closing tags is prose an author wrote, reproduced
> below word for word. That prose is where this page says whether the
> component has been reviewed. Read the children, not only the
> attributes.

<StubNotice
  name="divider"
  status="shipped"
  questions="[
  &#x22;The neutral hairline's rendered pixels have not been measured against a card, a sheet or the page, only its token pair is published.&#x22;,
  &#x22;Nobody has listened to a divider in a screen reader, so neither the separator role nor the labelled form's plain-text label is verified.&#x22;,
  &#x22;Whether an unlabelled separator should be announced at all, or hidden with every boundary carried by structure, is unresolved.&#x22;,
]"
>
  Audited against WCAG 2.2 AA. Clinical review pending.
</StubNotice>

## Preview [#preview]

<ComponentPreview name="divider" />

## Installation [#installation]

<ComponentInstall name="divider" unbuilt="false" importPath="@/components/ui/divider" />

## Usage [#usage]

```tsx
import { Divider } from "@/components/ui/divider"
```

```tsx
<Divider />
<Divider label="Earlier" />
<Divider orientation="vertical" />
```

## When to use it [#when-to-use-it]

<WhenToUse
  use="[
  &#x22;Between two stacked groups that are already distinct, to make the seam visible without giving either group a surface of its own.&#x22;,
  &#x22;As a named boundary in a list, with a short label such as \&#x22;Earlier\&#x22; or \&#x22;Today\&#x22;, echoing a grouping the list structure already carries.&#x22;,
  &#x22;As a thin vertical rule between a few inline facts, inside a flex row that gives the rule its height.&#x22;,
]"
  avoid="[
  { case: &#x22;You are grouping content that needs its own background, edge or padding, rather than a line between peers.&#x22;, instead: &#x22;card&#x22; },
  { case: &#x22;You are separating layers of the interface by depth, such as a panel above the page.&#x22;, instead: &#x22;surface&#x22; },
  { case: &#x22;You are offering a choice between mutually exclusive views and the lines are really the seams between options.&#x22;, instead: &#x22;segmented-control&#x22; },
]"
/>

## Anatomy [#anatomy]

<Anatomy
  name="divider"
  parts="[
  {
    name: &#x22;Divider&#x22;,
    describes: &#x22;The root. A neutral hairline on neither colour axis. Unlabelled, it is one element with role separator.&#x22;,
    prop: &#x22;orientation&#x22;,
  },
  {
    name: &#x22;Divider label&#x22;,
    describes: &#x22;Optional centred text on a horizontal rule, in muted ink. Its presence drops the separator role.&#x22;,
    prop: &#x22;label&#x22;,
  },
]"
/>

## Examples [#examples]

### A labelled boundary in a list [#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.

<ComponentPreview name="divider-a-labelled-group-boundary" kind="example" align="start" />

### A vertical rule between inline facts [#a-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.

<ComponentPreview name="divider-vertical-between-inline-facts" kind="example" align="start" />

## Content guidelines [#content-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.

<DoDont>
  <DoDont.Do>
    **A divider between two lists** that already carry their own structure, so the line is an echo.
  </DoDont.Do>

  <DoDont.Dont>
    **A divider between two runs of paragraphs** to imply sections a screen reader cannot hear.
  </DoDont.Dont>
</DoDont>

## Accessibility [#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"` with `aria-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-hidden` hairlines.
* 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.

<KeyboardTable
  name="divider"
  rows="[
  {
    keys: &#x22;Tab&#x22;,
    action: &#x22;Skips the divider&#x22;,
    notes: &#x22;Not focusable and not a tab stop, in either orientation, labelled or not.&#x22;,
  },
  {
    keys: &#x22;Shift + Tab&#x22;,
    action: &#x22;Skips the divider in reverse&#x22;,
    notes: &#x22;Focus moves straight to the interactive element before it, in visual order.&#x22;,
  },
]"
/>

<ContrastReport component="divider" />

## API reference [#api-reference]

<PropsTable name="DividerProps" />

`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 [#related]

* [Card](./card.mdx) groups content by giving it a background, an edge and padding. A divider only draws a line.
* [Surface](./surface.mdx) places a group on its own material at a chosen depth. A divider marks a seam between peers.
* [SegmentedControl](./segmented-control.mdx) offers a choice between views. Its seams look like dividers but carry a selection.
