Layout
Divider
A horizontal or vertical rule between sections, with an optional label such as "or".
- Built on
<hr>role="separator"aria-orientation- Client JavaScript
- None
- Works without React
- Yes, as HTML and CSS
- Source, gzipped
- 1.3 kB
- Dependencies
- React
or
Try its options
Loading its options.
Code
import { Button } from "@/components/equip/button";
import { Divider } from "@/components/equip/divider";
export default function Demo() {
return (
<div style={{ display: "grid", gap: 10, width: 208 }}>
<Button variant="outline">Continue with SSO</Button>
<Divider label="or" decorative />
<Button variant="ghost">Email me a sign-in link</Button>
</div>
);
}Your versionchange its options in Look, This part
HTMLworks in any framework, with the part's CSS
<div style="display:grid;gap:10px;width:208px">
<button type="button" class="eq-button" data-variant="outline" data-size="m">
<span class="eq-button-label">Continue with SSO</span>
</button>
<div data-orientation="horizontal" data-spacing="0" class="eq-divider" role="none" data-label-align="center">
<span class="eq-divider-label">or</span>
</div>
<button type="button" class="eq-button" data-variant="ghost" data-size="m">
<span class="eq-button-label">Email me a sign-in link</span>
</button>
</div>Examples
Between sections
Without a label it is a native hr, which assistive tech already announces as a separator.
Billing contact
Payment method
Mira Holt, finance@larder.example
Payment method
Visa ending 4242, expires 08/28
Archived
Mastercard ending 0019, expired 03/25
Code
import { Divider } from "@/components/equip/divider";
export default function Example() {
return (
<div style={{ width: "100%", maxWidth: 420, fontSize: "var(--eq-text-s)" }}>
<strong>Billing contact</strong>
<p style={{ margin: "4px 0 0", color: "var(--eq-ink-2)" }}>Mira Holt, finance@larder.example</p>
<Divider spacing={4} />
<strong>Payment method</strong>
<p style={{ margin: "4px 0 0", color: "var(--eq-ink-2)" }}>Visa ending 4242, expires 08/28</p>
<Divider spacing={4} label="Archived" labelAlign="start" />
<p style={{ margin: 0, color: "var(--eq-ink-3)" }}>Mastercard ending 0019, expired 03/25</p>
</div>
);
}HTMLworks in any framework, with the part's CSS
<div style="width:100%;max-width:420px;font-size:var(--eq-text-s)">
<strong>Billing contact</strong>
<p style="margin:4px 0 0;color:var(--eq-ink-2)">Mira Holt, finance@larder.example</p>
<hr data-orientation="horizontal" data-spacing="4" class="eq-divider">
<strong>Payment method</strong>
<p style="margin:4px 0 0;color:var(--eq-ink-2)">Visa ending 4242, expires 08/28</p>
<div data-orientation="horizontal" data-spacing="4" class="eq-divider" role="separator" aria-label="Archived" data-label-align="start">
<span class="eq-divider-label">Archived</span>
</div>
<p style="margin:0;color:var(--eq-ink-3)">Mastercard ending 0019, expired 03/25</p>
</div>Vertical, in a toolbar
A vertical rule stretches to the row's height and carries aria-orientation="vertical" unless it is decorative.
Saved
Code
import { Button } from "@/components/equip/button";
import { Divider } from "@/components/equip/divider";
export default function Example() {
return (
<div style={{ display: "flex", alignItems: "center", flexWrap: "wrap", gap: 4 }}>
<Button size="s" variant="ghost">Bold</Button>
<Button size="s" variant="ghost">Italic</Button>
<Divider orientation="vertical" spacing={1} />
<Button size="s" variant="ghost">Link</Button>
<Button size="s" variant="ghost">Quote</Button>
<Divider orientation="vertical" spacing={1} decorative />
<span style={{ fontSize: "var(--eq-text-xs)", color: "var(--eq-ink-2)" }}>Saved</span>
</div>
);
}HTMLworks in any framework, with the part's CSS
<div style="display:flex;align-items:center;flex-wrap:wrap;gap:4px">
<button type="button" class="eq-button" data-variant="ghost" data-size="s">
<span class="eq-button-label">Bold</span>
</button>
<button type="button" class="eq-button" data-variant="ghost" data-size="s">
<span class="eq-button-label">Italic</span>
</button>
<hr data-orientation="vertical" data-spacing="1" class="eq-divider" aria-orientation="vertical">
<button type="button" class="eq-button" data-variant="ghost" data-size="s">
<span class="eq-button-label">Link</span>
</button>
<button type="button" class="eq-button" data-variant="ghost" data-size="s">
<span class="eq-button-label">Quote</span>
</button>
<hr data-orientation="vertical" data-spacing="1" class="eq-divider" role="none">
<span style="font-size:var(--eq-text-xs);color:var(--eq-ink-2)">Saved</span>
</div>Props
| Prop | Type | Default | What it does |
|---|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" | A vertical rule stretches to the height of its flex row. |
label | ReactNode | none | Text set in the middle of a horizontal rule. Ignored on a vertical one. |
labelAlign | "start" | "center" | "end" | "center" | Where the label sits along the rule. |
decorative | boolean | false | Removes the separator role, for a rule that only decorates. A label is then read as plain text. |
spacing | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 0 | Space on both sides of the rule, from --eq-space-1 to --eq-space-6. |
...rest | ComponentProps<"hr"> | none | Every native attribute, including aria-label. |
Anatomy
| root | .eq-divider | An hr, or a div with role="separator" when it has a label. |
|---|---|---|
| label | .eq-divider-label | The lines either side are the root's ::before and ::after. |
Notes
- Renders on the server with no client JavaScript.
- Without a label it is a native hr, which is already a separator; a vertical one adds aria-orientation="vertical".
- A separator's content is not read out, so a string label becomes its aria-label. For a label that is not plain text, pass aria-label yourself.
- Between sign-in options, use decorative so "or" is read as ordinary text in the flow.
- It is not focusable and takes no input; a draggable divider is Split pane.
Source
Two files, and they are the whole part. Open either to read it, copy it straight from its bar, or add the part to your loadout and install it with everything else you picked.
components/equip/divider.tsx60 lines
import type { ComponentProps, ReactNode } from "react";
import "./divider.css";
export type DividerProps = Omit<ComponentProps<"hr">, "children"> & {
orientation?: "horizontal" | "vertical";
/** Text set in the middle of a horizontal rule, such as "or". */
label?: ReactNode;
/** Where the label sits along the rule. */
labelAlign?: "start" | "center" | "end";
/** Hides the rule from assistive tech when it only decorates and does not separate sections. */
decorative?: boolean;
/** Space on both sides of the rule, read from --eq-space-1 to --eq-space-6. */
spacing?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
};
export function Divider({
orientation = "horizontal",
label,
labelAlign = "center",
decorative = false,
spacing = 0,
className,
...rest
}: DividerProps) {
const classes = ["eq-divider", className].filter(Boolean).join(" ");
const data = {
"data-orientation": orientation,
"data-spacing": spacing,
};
const vertical = orientation === "vertical";
if (label == null || vertical) {
// hr is a separator on its own; aria-orientation is only needed when it stands upright.
return (
<hr
{...rest}
{...data}
className={classes}
role={decorative ? "none" : rest.role}
aria-orientation={!decorative && vertical ? "vertical" : rest["aria-orientation"]}
/>
);
}
// A separator's children are presentational, so the label text is also its name.
const name = typeof label === "string" ? label : undefined;
const { role, "aria-label": ariaLabel, ...divRest } = rest;
return (
<div
{...(divRest as ComponentProps<"div">)}
{...data}
className={classes}
role={decorative ? "none" : (role ?? "separator")}
aria-label={decorative ? undefined : (ariaLabel ?? name)}
data-label-align={labelAlign}
>
<span className="eq-divider-label">{label}</span>
</div>
);
}components/equip/divider.css60 lines
@layer equip {
.eq-divider {
--_space: 0px;
box-sizing: border-box;
flex: none;
align-self: stretch;
width: auto;
height: 1px;
margin: var(--_space) 0;
padding: 0;
border: 0;
background: var(--eq-line);
color: var(--eq-ink-2);
}
.eq-divider[data-orientation="vertical"] {
width: 1px;
height: auto;
min-height: 1em;
margin: 0 var(--_space);
}
.eq-divider[data-spacing="1"] { --_space: var(--eq-space-1); }
.eq-divider[data-spacing="2"] { --_space: var(--eq-space-2); }
.eq-divider[data-spacing="3"] { --_space: var(--eq-space-3); }
.eq-divider[data-spacing="4"] { --_space: var(--eq-space-4); }
.eq-divider[data-spacing="5"] { --_space: var(--eq-space-5); }
.eq-divider[data-spacing="6"] { --_space: var(--eq-space-6); }
/* With a label the root is a row: a line, the label, a line. */
.eq-divider[data-label-align] {
display: flex;
align-items: center;
gap: var(--eq-space-3);
height: auto;
background: none;
font: var(--eq-weight) var(--eq-text-s) / 1.4 var(--eq-font);
}
.eq-divider[data-label-align]::before,
.eq-divider[data-label-align]::after {
content: "";
flex: 1 1 0;
height: 1px;
background: var(--eq-line);
}
.eq-divider[data-label-align="start"]::before,
.eq-divider[data-label-align="end"]::after {
flex: 0 0 var(--eq-space-4);
}
.eq-divider-label {
flex: 0 1 auto;
min-width: 0;
text-align: center;
overflow-wrap: anywhere;
}
}