Separator
A simple horizontal or vertical divider line used to visually separate sections of content or groups of related elements.
Use Cases
- Separate sections of a settings page or form
- Divide navigation groups in a sidebar menu
- Break apart card content into logical blocks
- Create visual distinction between list items
- Split a layout into left and right panels with a vertical divider
- Provide an "or" divider between alternative actions (e.g., social login vs email)
- Separate header, body, and footer areas within a modal or card
Simplest Implementation
export function Separator() {
return <hr className="border-t border-gray-200" />;
}A plain horizontal rule styled with Tailwind. No "use client" is needed since there is no interactivity. The border-t ensures only the top border renders, avoiding the double-line effect of a default <hr>.
Variations
Horizontal Separator
interface SeparatorProps {
className?: string;
}
export function Separator({ className }: SeparatorProps) {
return (
<hr
role="separator"
aria-orientation="horizontal"
className={`border-t border-gray-200 ${className ?? ""}`}
/>
);
}Adds explicit ARIA attributes so assistive technologies announce the divider correctly. The className prop allows consumers to override spacing or color on a per-use basis.
Vertical Separator
interface SeparatorProps {
className?: string;
}
export function VerticalSeparator({ className }: SeparatorProps) {
return (
<div
role="separator"
aria-orientation="vertical"
className={`h-full w-px bg-gray-200 ${className ?? ""}`}
/>
);
}
// Usage inside a flex row
function Toolbar() {
return (
<div className="flex h-10 items-center gap-4">
<button className="text-sm">Cut</button>
<VerticalSeparator />
<button className="text-sm">Copy</button>
<VerticalSeparator />
<button className="text-sm">Paste</button>
</div>
);
}A vertical divider rendered as a div with w-px width and h-full to stretch to the parent height. It must live inside a flex container with a defined height, otherwise the separator collapses to zero.
With Text Label
interface LabeledSeparatorProps {
label: string;
}
export function LabeledSeparator({ label }: LabeledSeparatorProps) {
return (
<div className="flex items-center gap-4">
<div className="h-px flex-1 bg-gray-200" />
<span className="text-sm text-gray-500">{label}</span>
<div className="h-px flex-1 bg-gray-200" />
</div>
);
}
// Usage
<LabeledSeparator label="or" />The classic "or" divider pattern. Two flex-1 lines expand equally on each side of the centered text. Using h-px instead of border-t gives more predictable alignment when flex items have varied heights.
Dashed Style
interface SeparatorProps {
className?: string;
}
export function DashedSeparator({ className }: SeparatorProps) {
return (
<hr
role="separator"
className={`border-t border-dashed border-gray-300 ${className ?? ""}`}
/>
);
}The border-dashed utility creates a dotted divider that feels lighter than a solid line. Useful in form sections or settings panels where a subtle separation is needed without hard visual boundaries.
With Spacing Variants
type SpacingSize = "none" | "sm" | "md" | "lg" | "xl";
interface SeparatorProps {
spacing?: SpacingSize;
className?: string;
}
const spacingClasses: Record<SpacingSize, string> = {
none: "my-0",
sm: "my-2",
md: "my-4",
lg: "my-6",
xl: "my-8",
};
export function Separator({ spacing = "md", className }: SeparatorProps) {
return (
<hr
role="separator"
className={`border-t border-gray-200 ${spacingClasses[spacing]} ${className ?? ""}`}
/>
);
}Predefined spacing presets prevent ad-hoc margin values from appearing throughout the codebase. The none option is useful when the parent container already manages spacing via gap or padding.
Decorative Gradient
interface SeparatorProps {
className?: string;
}
export function GradientSeparator({ className }: SeparatorProps) {
return (
<div
role="separator"
aria-orientation="horizontal"
className={`h-px bg-gradient-to-r from-transparent via-gray-300 to-transparent ${className ?? ""}`}
/>
);
}A gradient line that fades in from both edges, creating a softer visual separation. Uses Tailwind's via- modifier for a three-stop gradient. This works best in hero sections or between large content blocks where a traditional rule feels too heavy.
Complex Implementation
import { forwardRef } from "react";
type Orientation = "horizontal" | "vertical";
type Variant = "solid" | "dashed" | "dotted" | "gradient";
type Thickness = "thin" | "medium" | "thick";
type SpacingSize = "none" | "sm" | "md" | "lg" | "xl";
interface SeparatorProps {
orientation?: Orientation;
variant?: Variant;
thickness?: Thickness;
spacing?: SpacingSize;
label?: string;
color?: string;
className?: string;
}
const spacingMap: Record<Orientation, Record<SpacingSize, string>> = {
horizontal: { none: "", sm: "my-2", md: "my-4", lg: "my-6", xl: "my-8" },
vertical: { none: "", sm: "mx-2", md: "mx-4", lg: "mx-6", xl: "mx-8" },
};
const thicknessMap: Record<Orientation, Record<Thickness, string>> = {
horizontal: { thin: "h-px", medium: "h-0.5", thick: "h-1" },
vertical: { thin: "w-px", medium: "w-0.5", thick: "w-1" },
};
export const Separator = forwardRef<HTMLDivElement, SeparatorProps>(
function Separator(
{
orientation = "horizontal",
variant = "solid",
thickness = "thin",
spacing = "md",
label,
color = "gray-200",
className,
},
ref
) {
const spacingClass = spacingMap[orientation][spacing];
if (label && orientation === "horizontal") {
return (
<div
ref={ref}
role="separator"
aria-orientation="horizontal"
className={`flex items-center gap-4 ${spacingClass} ${className ?? ""}`}
>
<SeparatorLine
orientation="horizontal"
variant={variant}
thickness={thickness}
color={color}
/>
<span className="shrink-0 text-sm text-gray-500">{label}</span>
<SeparatorLine
orientation="horizontal"
variant={variant}
thickness={thickness}
color={color}
/>
</div>
);
}
return (
<SeparatorLine
ref={ref}
orientation={orientation}
variant={variant}
thickness={thickness}
color={color}
className={`${spacingClass} ${className ?? ""}`}
/>
);
}
);
interface LineProps {
orientation: Orientation;
variant: Variant;
thickness: Thickness;
color: string;
className?: string;
}
const SeparatorLine = forwardRef<HTMLDivElement, LineProps>(
function SeparatorLine({ orientation, variant, thickness, color, className }, ref) {
const size = thicknessMap[orientation][thickness];
const stretch = orientation === "horizontal" ? "w-full" : "h-full";
if (variant === "gradient") {
const direction = orientation === "horizontal" ? "bg-gradient-to-r" : "bg-gradient-to-b";
return (
<div
ref={ref}
role="separator"
aria-orientation={orientation}
className={`${size} ${stretch} ${direction} from-transparent via-${color} to-transparent ${className ?? ""}`}
/>
);
}
const borderSide = orientation === "horizontal" ? "border-t" : "border-l";
const borderStyle =
variant === "dashed" ? "border-dashed" : variant === "dotted" ? "border-dotted" : "";
const borderThickness =
thickness === "thick" ? `${borderSide}-4` : thickness === "medium" ? `${borderSide}-2` : borderSide;
return (
<div
ref={ref}
role="separator"
aria-orientation={orientation}
className={`${stretch} ${borderThickness} border-${color} ${borderStyle} ${className ?? ""}`}
/>
);
}
);Key aspects:
- Dual orientation -- a single component handles both horizontal and vertical dividers by swapping dimension and border-direction classes based on the
orientationprop. - Label support -- when a
labelis provided on a horizontal separator, the component splits into two lines flanking the centered text, using the sameSeparatorLinesub-component for consistency. - Variant system --
solid,dashed,dotted, andgradientcover the most common visual styles. The gradient variant uses a completely different rendering path (background gradient vs border) to avoid conflicting CSS properties. - Proportional thickness -- border-based variants map thickness to
border-t,border-t-2, andborder-t-4, while gradient variants useh-px,h-0.5, andh-1for equivalent visual weight. - forwardRef -- both the outer
Separatorand innerSeparatorLineaccept refs, allowing animation libraries or measurement logic to attach directly to the DOM element. - Spacing maps per orientation -- horizontal separators use vertical margin (
my-) and vertical separators use horizontal margin (mx-), so spacing always applies in the correct axis.
Gotchas
-
Vertical separator collapses to zero height -- a vertical separator with
h-fullrequires a parent with an explicit height or a flex container. Without it, the divider is invisible. -
Tailwind purge strips dynamic color classes -- interpolating
border-${color}orvia-${color}into class strings causes Tailwind to purge those classes in production. Use a safelist or a static class map instead. -
<hr>has default margin -- browsers apply a default margin to<hr>elements. If you do not reset it withmy-0or your own margin class, you get unexpected extra spacing. -
ARIA role on decorative dividers -- purely decorative separators should use
role="none"oraria-hidden="true"instead ofrole="separator"to avoid cluttering the accessibility tree with meaningless landmarks. -
Gradient direction ignored in dark mode -- if using
via-gray-300for the gradient midpoint, it may become invisible on dark backgrounds. Swap tovia-gray-600in dark mode using Tailwind'sdark:prefix. -
Border-dashed rendering varies by browser -- the length and gap of dashes differ between Chrome, Firefox, and Safari. If pixel-perfect dashes are required, use an SVG pattern or a repeating background image instead.
Related
- Card -- Separators are often used inside cards to divide header, body, and footer
- Sidebar -- Vertical and horizontal separators divide nav groups in sidebars
- Modal -- Modals use separators between title, content, and action areas
- Tabs -- A separator below the tab bar distinguishes tabs from panel content