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

Mira Holt, finance@larder.example


Payment method

Visa ending 4242, expires 08/28

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

PropTypeDefaultWhat it does
orientation"horizontal" | "vertical""horizontal"A vertical rule stretches to the height of its flex row.
labelReactNodenoneText 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.
decorativebooleanfalseRemoves the separator role, for a rule that only decorates. A label is then read as plain text.
spacing0 | 1 | 2 | 3 | 4 | 5 | 60Space on both sides of the rule, from --eq-space-1 to --eq-space-6.
...restComponentProps<"hr">noneEvery native attribute, including aria-label.

Anatomy

root.eq-dividerAn hr, or a div with role="separator" when it has a label.
label.eq-divider-labelThe 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;
  }
}

EqUIp is in beta: free for non-commercial use, no warranty, and things can change. Terms

Nothing picked yet. Press A on a part, or use the plus on the board, and the install command builds here.

Loading the index.

Loading the index