Skip to content

Separator

A hairline that divides content into groups: horizontally between sections, vertically between inline items.

SourceBase UIRegistry

prfct

A design system for Next.js, built on shadcn.

DocsComponentsThemes
import { Separator } from "@/components/ui/separator"

export default function SeparatorDemo() {
  return (
    <div className="w-full max-w-80">
      <div className="flex flex-col gap-1">
        <p className="text-sm font-semibold text-foreground">prfct</p>
        <p className="text-sm text-muted-foreground">
          A design system for Next.js, built on shadcn.
        </p>
      </div>
      <Separator className="my-4" />
      <div className="flex h-5 items-center gap-4 text-sm text-muted-foreground">
        <span>Docs</span>
        <Separator orientation="vertical" />
        <span>Components</span>
        <Separator orientation="vertical" />
        <span>Themes</span>
      </div>
    </div>
  )
}

Installation

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/separator.json

Usage

import { Separator } from "@/components/ui/separator"
<Separator />
<Separator orientation="vertical" />

A separator is 1px of the border token. Horizontal separators fill their container's width; vertical ones stretch to the height of their flex row, so give the row a height (h-5) and the separators follow.

Examples

Toolbar groups

Vertical separators split related controls into groups without adding boxes around them. When a separator should be shorter than its row, set its height and align it with data-vertical:self-center — the variant is needed to override the default stretch.

import {
  AlignCenterIcon,
  AlignLeftIcon,
  AlignRightIcon,
  BoldIcon,
  ItalicIcon,
  LinkIcon,
  UnderlineIcon,
} from "lucide-react"

import { Button } from "@/components/ui/button"
import { Separator } from "@/components/ui/separator"

const groups = [
  [
    { label: "Bold", icon: BoldIcon },
    { label: "Italic", icon: ItalicIcon },
    { label: "Underline", icon: UnderlineIcon },
  ],
  [
    { label: "Align left", icon: AlignLeftIcon },
    { label: "Align center", icon: AlignCenterIcon },
    { label: "Align right", icon: AlignRightIcon },
  ],
  [{ label: "Insert link", icon: LinkIcon }],
]

export default function SeparatorToolbar() {
  return (
    <div
      role="group"
      aria-label="Text formatting"
      className="flex h-11 items-center gap-1 rounded-xl border bg-card px-1.5 shadow-raised"
    >
      {groups.map((group, index) => (
        <div key={index} className="flex items-center gap-1">
          {index > 0 && (
            <Separator
              orientation="vertical"
              className="mx-1 h-5 data-vertical:self-center"
            />
          )}
          {group.map(({ label, icon: Icon }) => (
            <Button
              key={label}
              variant="ghost"
              size="icon-sm"
              aria-label={label}
            >
              <Icon />
            </Button>
          ))}
        </div>
      ))}
    </div>
  )
}

Lists

Between rows of a list or settings panel, separators do the work of borders without doubling up on the container's edge.

import { Fragment } from "react"

import { Separator } from "@/components/ui/separator"
import { Switch } from "@/components/ui/switch"

const settings = [
  {
    id: "mentions",
    label: "Mentions",
    description: "When someone @mentions you.",
  },
  {
    id: "replies",
    label: "Replies",
    description: "Replies to threads you follow.",
  },
  {
    id: "digest",
    label: "Weekly digest",
    description: "A summary every Monday.",
  },
]

export default function SeparatorList() {
  return (
    <div className="w-full max-w-sm rounded-xl border bg-card shadow-raised">
      {settings.map((setting, index) => (
        <Fragment key={setting.id}>
          {index > 0 && <Separator />}
          <div className="flex items-center justify-between gap-6 px-4 py-3.5">
            <label htmlFor={setting.id} className="flex flex-col gap-0.5">
              <span className="text-sm font-medium text-foreground">
                {setting.label}
              </span>
              <span className="text-body-xs text-muted-foreground">
                {setting.description}
              </span>
            </label>
            <Switch id={setting.id} defaultChecked={index !== 2} />
          </div>
        </Fragment>
      ))}
    </div>
  )
}

With a label

For an or between alternatives, use FieldSeparator. It composes two separators around a label and needs no background, so it sits correctly on any surface.

or
import { Button } from "@/components/ui/button"
import {
  Field,
  FieldGroup,
  FieldLabel,
  FieldSeparator,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input"

export default function SeparatorWithLabel() {
  return (
    <FieldGroup className="w-full max-w-72">
      <Button variant="outline" className="w-full">
        Continue with GitHub
      </Button>
      <FieldSeparator>or</FieldSeparator>
      <Field>
        <FieldLabel htmlFor="separator-email">Work email</FieldLabel>
        <Input
          id="separator-email"
          type="email"
          placeholder="you@company.com"
        />
      </Field>
      <Button className="w-full">Continue with email</Button>
    </FieldGroup>
  )
}

Decorative

Separators are semantic by default. When one only exists for rhythm — between inline metadata, for example — mark it decorative so screen readers don't announce it.

Designing with nothing left to take away

AKAna Kowalska
6 min read
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { Separator } from "@/components/ui/separator"

export default function SeparatorDecorative() {
  return (
    <article className="flex w-full max-w-md flex-col gap-3">
      <h3 className="text-heading-sm text-foreground">
        Designing with nothing left to take away
      </h3>
      <div className="flex h-5 items-center gap-3 text-body-xs text-muted-foreground">
        <span className="flex items-center gap-2">
          <Avatar size="xs">
            <AvatarFallback>AK</AvatarFallback>
          </Avatar>
          Ana Kowalska
        </span>
        {/* Purely visual dividers: hidden from assistive technology. */}
        <Separator
          orientation="vertical"
          decorative
          className="h-3.5 data-vertical:self-center"
        />
        <time dateTime="2026-09-18">Sep 18, 2026</time>
        <Separator
          orientation="vertical"
          decorative
          className="h-3.5 data-vertical:self-center"
        />
        <span>6 min read</span>
      </div>
    </article>
  )
}
<Separator orientation="vertical" decorative />

Guidelines

When to use

  • To divide a surface into groups whose relationship isn't already clear from spacing or headings.
  • Between groups of toolbar controls, menu sections and list rows.
  • Between inline metadata where commas or bullets would be noisy.

When not to use

  • Where whitespace alone would separate the groups. Space is the quieter tool — reach for it first.
  • As the edge of a surface — cards, dialogs and tables already draw their own borders.
  • To separate sections of a page that have headings; the heading is the separator.

Prefer space, then lines

prfct layouts get their structure from the spacing scale. Add a separator only when grouping by space would need so much of it that the layout falls apart, or when items are too dense to separate any other way.

ProfileName, photo, bio
SecurityPassword, 2FA
DoSpacing does the grouping; the page stays calm.
ProfileName, photo, bioSecurityPassword, 2FA
Don’tA line under every item adds noise without adding meaning.

Stay on the border token

Separators use bg-border, the subtle structural color. Don't darken them to create emphasis — if a division needs to be stronger, it probably needs a heading or more space instead.

Accessibility

prfct's separator renders a <div role="separator"> with aria-orientation, so assistive technology can announce a boundary between groups. It is not focusable and has no keyboard interaction.

  • Semantic vs decorative. Keep the default role when the separator marks a real boundary between groups of content. Use decorative when it is purely visual: it renders role="none" and drops aria-orientation, which that role can't carry.
  • Non-text contrast. The border token is intentionally subtle and isn't required to meet 3:1 — a separator never carries information on its own. Don't rely on one as the only indicator of a state change.

API reference

Separator

Renders a <div>. Accepts every prop of Base UI Separator.

PropTypeDefault
orientation

Direction of the line. Also sets aria-orientation and data-orientation.

"horizontal" | "vertical""horizontal"
decorative

Hides a purely visual separator from assistive technology: renders role="none" without aria-orientation.

booleanfalse
render

Replaces the rendered element while keeping behavior and styles.

ReactElement | (props, state) => ReactElementNo default