Divider

Horizontal content divider with single-line, double-line, filled, and aligned variants.

Variants

Single, double, filled, and with content.

or
Recent
function DividerVariants() {
  return (
    <div className="flex w-full max-w-md flex-col gap-6">
      <Divider />
      <Divider variant="double" />
      <Divider variant="fill" />
      <Divider>or</Divider>
      <Divider align="start">Recent</Divider>
    </div>
  );
}
function DividerVariants() {
  return (
    <div className="flex w-full max-w-md flex-col gap-6">
      <Divider />
      <Divider variant="double" />
      <Divider variant="fill" />
      <Divider>or</Divider>
      <Divider align="start">Recent</Divider>
    </div>
  );
}

Installation

npx shadcn@latest add https://boardcn.dev/r/divider.json
npx shadcn@latest add https://boardcn.dev/r/divider.json

BoardCN dependencies

The CLI installs these for you — you do not need to add them yourself.

Source

The file the CLI copies into your project.

components/base/divider/divider.tsx
import type { HTMLAttributes, ReactNode, Ref } from "react";
import { cx, sortCx } from "@/utils/cx";

export type DividerVariant = "single" | "double" | "fill";
export type DividerAlign = "start" | "center" | "end";

export interface DividerProps
  extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
  /** Single hairline, two framing hairlines, or a soft filled strip. */
  variant?: DividerVariant;
  /** Positions the divider content while keeping the remaining line flexible. */
  align?: DividerAlign;
  /** Text, a button, or any compact control placed in the divider. */
  children?: ReactNode;
  /** Extra classes for the content wrapper. */
  contentClassName?: string;
  ref?: Ref<HTMLDivElement>;
}

const styles = sortCx({
  root: "w-full",
  line: "h-px min-w-0 flex-1 bg-separator-border",
  content: "shrink-0 text-body-medium text-text-secondary",
  align: {
    start: "justify-start",
    center: "justify-center",
    end: "justify-end",
  },
  withContent: {
    single: "flex items-center gap-3",
    double: "flex items-center border-y border-separator-border py-2.5",
    fill: "flex items-center rounded-2lg bg-background-secondary-default px-4 py-2.5",
  },
  empty: {
    single: "h-px bg-separator-border",
    double: "h-2 border-y border-separator-border",
    fill: "h-2 rounded-full bg-background-secondary-default",
  },
});

/**
 * Horizontal content divider with three BoardCN surface treatments.
 *
 * `single` places content within one continuous hairline, `double` frames it
 * with a line above and below, and `fill` uses the secondary surface color.
 * Children stay completely composable, so labels, buttons, and small controls
 * can share the same primitive.
 */
export function Divider({
  variant = "single",
  align = "center",
  children,
  contentClassName,
  className,
  ref,
  ...props
}: DividerProps) {
  const hasContent = children !== undefined && children !== null;

  if (!hasContent) {
    return (
      <div
        ref={ref}
        role="separator"
        aria-orientation="horizontal"
        data-variant={variant}
        className={cx(styles.root, styles.empty[variant], className)}
        {...props}
      />
    );
  }

  const showLeadingLine = variant === "single" && align !== "start";
  const showTrailingLine = variant === "single" && align !== "end";

  return (
    <div
      ref={ref}
      data-variant={variant}
      data-align={align}
      className={cx(
        styles.root,
        styles.withContent[variant],
        styles.align[align],
        className,
      )}
      {...props}
    >
      {showLeadingLine ? <span aria-hidden className={styles.line} /> : null}
      <div className={cx(styles.content, contentClassName)}>{children}</div>
      {showTrailingLine ? <span aria-hidden className={styles.line} /> : null}
    </div>
  );
}
import type { HTMLAttributes, ReactNode, Ref } from "react";
import { cx, sortCx } from "@/utils/cx";

export type DividerVariant = "single" | "double" | "fill";
export type DividerAlign = "start" | "center" | "end";

export interface DividerProps
  extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
  /** Single hairline, two framing hairlines, or a soft filled strip. */
  variant?: DividerVariant;
  /** Positions the divider content while keeping the remaining line flexible. */
  align?: DividerAlign;
  /** Text, a button, or any compact control placed in the divider. */
  children?: ReactNode;
  /** Extra classes for the content wrapper. */
  contentClassName?: string;
  ref?: Ref<HTMLDivElement>;
}

const styles = sortCx({
  root: "w-full",
  line: "h-px min-w-0 flex-1 bg-separator-border",
  content: "shrink-0 text-body-medium text-text-secondary",
  align: {
    start: "justify-start",
    center: "justify-center",
    end: "justify-end",
  },
  withContent: {
    single: "flex items-center gap-3",
    double: "flex items-center border-y border-separator-border py-2.5",
    fill: "flex items-center rounded-2lg bg-background-secondary-default px-4 py-2.5",
  },
  empty: {
    single: "h-px bg-separator-border",
    double: "h-2 border-y border-separator-border",
    fill: "h-2 rounded-full bg-background-secondary-default",
  },
});

/**
 * Horizontal content divider with three BoardCN surface treatments.
 *
 * `single` places content within one continuous hairline, `double` frames it
 * with a line above and below, and `fill` uses the secondary surface color.
 * Children stay completely composable, so labels, buttons, and small controls
 * can share the same primitive.
 */
export function Divider({
  variant = "single",
  align = "center",
  children,
  contentClassName,
  className,
  ref,
  ...props
}: DividerProps) {
  const hasContent = children !== undefined && children !== null;

  if (!hasContent) {
    return (
      <div
        ref={ref}
        role="separator"
        aria-orientation="horizontal"
        data-variant={variant}
        className={cx(styles.root, styles.empty[variant], className)}
        {...props}
      />
    );
  }

  const showLeadingLine = variant === "single" && align !== "start";
  const showTrailingLine = variant === "single" && align !== "end";

  return (
    <div
      ref={ref}
      data-variant={variant}
      data-align={align}
      className={cx(
        styles.root,
        styles.withContent[variant],
        styles.align[align],
        className,
      )}
      {...props}
    >
      {showLeadingLine ? <span aria-hidden className={styles.line} /> : null}
      <div className={cx(styles.content, contentClassName)}>{children}</div>
      {showTrailingLine ? <span aria-hidden className={styles.line} /> : null}
    </div>
  );
}

Props

Generated from the component's TypeScript types. Standard DOM and React Aria props are omitted.

Divider

Horizontal content divider with three BoardCN surface treatments. `single` places content within one continuous hairline, `double` frames it with a line above and below, and `fill` uses the secondary surface color. Children stay completely composable, so labels, buttons, and small controls can share the same primitive.

PropTypeDefaultDescription
align"start" | "center" | "end"centerPositions the divider content while keeping the remaining line flexible.
childrenReactNodeText, a button, or any compact control placed in the divider.
contentClassNamestringExtra classes for the content wrapper.
variant"single" | "double" | "fill"singleSingle hairline, two framing hairlines, or a soft filled strip.