Skip to content
Ballpoint
Contents

Separator

A gently wandering pen rule, horizontal or vertical.

Built on Base UI Separator

Ballpoint

Components drawn in blue ballpoint.

DocsComponentsSource

Installation

pnpm dlx shadcn@latest add @ballpoint/separator
Or copy the source
ui/separator.tsx
"use client";

import { useMemo } from "react";
import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
import { cn } from "@/lib/utils";
import { penStyle, useInkBox, useInkSeed, usePen, type Pen } from "@/hooks/use-ink-box";
import { InkSvg, Stroke } from "@/lib/ink";
import { ruleStroke, verticalStroke } from "@/lib/ink-sketch";

// Drawn long and stretched along its length only, so the line keeps its
// weight at any size and never needs redrawing as it resizes.
const LENGTH = 700;

function Separator({
  className,
  orientation = "horizontal",
  seed,
  draw,
  weight,
  speed,
  ...props
}: SeparatorPrimitive.Props & Pick<Pen, "draw" | "weight" | "speed"> & { seed?: string | number }) {
  const pen = usePen({ draw, weight, speed });
  const s = useInkSeed(seed);
  const [ref] = useInkBox([LENGTH, 4]);
  const horizontal = orientation === "horizontal";
  const d = useMemo(() => (horizontal ? ruleStroke(s, LENGTH) : verticalStroke(s, LENGTH)), [horizontal, s]);
  const mode = pen.draw ?? "auto";
  return (
    <SeparatorPrimitive
      data-slot="separator"
      orientation={orientation}
      className={cn(
        "relative shrink-0 text-ink-4 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:self-stretch",
        className,
      )}
      {...props}
    >
      <InkSvg
        ref={ref}
        pending={mode === "auto"}
        box={horizontal ? [0, -1, LENGTH, 5] : [0, -1, 3, LENGTH + 2]}
        stretch
        className={horizontal ? "inset-x-0 -top-0.5 h-[5px] w-full" : "-left-[1px] inset-y-0 h-full w-[3px]"}
        style={penStyle(pen)}
      >
        <Stroke d={d} draw={mode} duration={700} width={1.1} />
      </InkSvg>
    </SeparatorPrimitive>
  );
}

export { Separator };

Usage

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

<Separator />
<Separator orientation="vertical" />

API reference

orientation"horizontal" | "vertical"default "horizontal"
Which way the rule runs.
seedstring | number
Pins the drawing.

Every other prop goes to Base UI's Separator.

Pen settings

The drawing also takes draw, weight and speed, as props or from the nearest InkProvider. What each one does.