Borders, gradients, pseudo-elements and clipped shapes can all split a page into sections without a single image file. Plenty of pages add one out of habit, though, when plain spacing would have done the job.
MDN Web Docs (2026) describes the hr element as a thematic break, not a decorative line, and points purely visual rules toward CSS borders. A divider earns its place where it marks a boundary that spacing alone fails to show.
Which Method Should You Use to Build a CSS Divider?
It depends on the job. A plain break between two blocks takes an hr or a border. Anything curved needs SVG, and a label in the middle calls for a pseudo-element. The table puts them side by side.
| Method | Best for | Main limit | Extra markup |
|---|---|---|---|
| hr or border | Plain line between content blocks | Solid or dashed styles, no fade | None or one div |
| Pseudo-element | Lines beside text or an icon | Needs a flex wrapper | One wrapper |
| Gradient | Fading, striped, or scalloped lines | Awkward for large curves | None or one div |
| clip-path | Diagonals and triangles | Clips borders and children too | None |
| Inline SVG | Waves and smooth curves | Path data is tedious to write by hand | One svg element |
Most pages only ever need the first row. A styled hr covers blog posts and docs, and settings screens usually get by with it too. The other methods pay for their extra markup on landing pages and section edges.
A divider also has to strengthen the visual hierarchy of the page, and sometimes plain spacing does that job better than a line.
Curves are the one case where a border cannot help at all. Those need SVG paths, which stay sharp at any container width.
How to Style an hr Element as a Divider
Wipe the default border first, then draw your own with border-top and set the width and margin around it.
The rendering section of the HTML Living Standard (WHATWG, revision of 13 October 2026) gives the hr a 1px inset border and an auto inline margin, plus a gray color. MDN adds that the element is defined by meaning now, which is why purely visual lines belong to CSS borders.
- Remove the default inset border with
border: none. - Draw the line with
border-top: 2px solid #ccc, which sets thickness, style, and color in one declaration. - Set the length with
width: 60%and center it withmargin-inline: auto. - Add vertical spacing with
margin-block: 2rem. - Soften the line with
opacity: 0.6if it competes with the text.
That margin does more work than the line itself. The white space around a divider is what makes it read as a break, so I settle on 1.5rem to 3rem before touching line weight.
Why color alone does not work
This one catches almost everyone the first time. Setting only a color on an hr changes the line, but the inset style still paints a shaded edge on all four sides, 1px each, so the rule stays 2px tall.
Declare border-style or border-top yourself and the shading goes away. The old color and noshade attributes work by switching the style to solid, which tells you what the real fix is.
Line styles beyond solid
Signicat’s Catnip design system renders a native hr and maps its style setting straight to border-style: solid, dashed, dotted, or double. Its size setting maps to 1px, 2px, and 4px of thickness.
Dashed works for secondary breaks, and dotted is about the lightest touch you can get. Double reads as a formal, print-style rule. For that one, copy MDN’s own hr example, which uses a 3px double border.
How to Make a Divider With Text or an Icon in the Middle
See the Pen
Text Divider by Lynden Oliver (@lyndenoliver)
on CodePen.
An hr cannot contain text, so build a labeled divider from a flex row with a line on each side of the label.
Set the wrapper to display flex with align-items center and a gap, then give ::before and ::after an empty content string, flex: 1, and a border-top. The two lines share whatever width the label leaves over.
Tabler’s divider component shows how far the pattern stretches. Its hr-text class centers the label by default, and hr-text-start and hr-text-end move it to either end (Tabler documentation).
For an icon, swap the text for a span that holds the icon and keep the same two lines.
The fieldset and legend alternative
A fieldset with only a top border and a centered legend draws the same look with no pseudo-elements (Kindacode’s Tailwind example). The HTML Standard skips painting the fieldset border behind the legend, so the line breaks around the label on its own.
The cost is meaning. A fieldset groups form controls and its legend names that group, so using the pair for decoration sends the wrong signal.
The flex row works inside any container and keeps both lines level at every width, and moving the label takes one alignment change. It does need a wrapper element and a class on every divider. The fieldset route is a single border with no generated content, and the browser handles label placement. Its downsides are the wrong semantics for decoration and a legend position tied to the fieldset border.
One accessibility catch applies to both. MDN notes that browsers treat everything inside a separator role as presentational, so a heading placed inside role=”separator” loses its heading semantics (MDN Web Docs).
Keep the label as plain text and leave the role off the labeled wrapper.
How to Create Gradient, Fading, and Patterned Dividers
See the Pen
Fading Divider by Steven Mask (@mnicpt)
on CodePen.
A border can’t fade out at the ends. A gradient can, and it can also repeat in custom stripes or shift color along the length.
For the fade, give a thin element a background of linear-gradient from transparent to a color and back to transparent. Webstudio’s separator docs set it at 2px tall, 50% wide, with 32px of auto-centered margin.
Tuning color stops by eye gets tedious, so a CSS gradient generator speeds up the fade and stripe variants.
For custom dashes, repeating-linear-gradient sets dash and gap lengths exactly. A radial-gradient tiled across the width draws a scalloped edge, and border-image accepts a gradient as its source if you want a gradient border.
Double lines come from a different trick. Put a box-shadow with zero blur and a 4px vertical offset on a 1px element, and the shadow paints the second line, which a box shadow generator lets you preview before you commit.
border-image has a catch, though. MDN states that border-radius has no effect on a border image, so a rounded gradient border needs two stacked background-image layers instead.
Some browsers also skip the image when border-style is none or the border width is 0, which is why MDN advises declaring a fallback border-style. The property itself has been Baseline since July 2015.
How to Build Wave, Curve, and Diagonal Section Dividers
Inline SVG handles waves and curves. Diagonals and triangles come from clip-path with polygon().
SVG wave dividers
See the Pen
SVG section dividers, embedded with Data-URI & colored with SASS by rinaw (@rinaw)
on CodePen.
Place the SVG at the bottom edge of the section and give it a viewBox with no fixed width or height. Set it to width 100% and the shape stretches to fill any container (CodeShack).
Fill the path with the next section’s background color, ideally through a custom property such as var(–color-bg). A wave that belongs on a top edge just needs transform: rotate(180deg), and display: block on the svg removes the small gap inline elements leave below them.
Hand-writing cubic bezier path data is tedious, because changing the wave height means recalculating every control point (parix.ai). An SVG waves generator outputs a copyable viewBox path and skips that step.
clip-path diagonals and triangles
See the Pen
Diagonal Divider by JD Tinney (@Yappenzo)
on CodePen.
The clip-path property has been Baseline widely available since January 2020 (MDN Web Docs), so a polygon() diagonal needs no fallback in current browsers.
A triangle edge is clip-path: polygon(50% 0, 100% 100%, 0 100%), the same shape MDN uses in its own example. Stick to percentages, because MDN’s shape() versus path() comparison shows a path() clip stays fixed in pixels while the element grows.
Watch what gets cut. The clip applies to the element’s border and text as well as its background, so any content near the cut edge disappears with it. Any value other than none also creates a stacking context, the same way opacity does, which can change z-index layering between neighboring sections.
Dragging points in a CSS clip-path generator is faster than guessing polygon coordinates, and you can paste the finished values straight into the stylesheet.
How to Add Vertical Dividers in Flex, Grid, and Column Layouts
See the Pen
Untitled by Bobby (@protonbobby)
on CodePen.
An hr only draws horizontal lines, so a vertical divider is usually a border on one item. A thin stretched element or a column rule also works.
Prefer border-inline-start over border-left. Tailwind’s documentation notes that the logical property maps to the left or right edge based on text direction, so the line follows the layout in right-to-left languages.
Flexbox and grid
In a flex row, give every item after the first a border-inline-start plus matching padding, or drop a thin element between items.
A standalone line needs height to stretch into. Chi, the design system from Lumen, states that its vertical divider requires a parent container with a specified height.
A grid with one row can use border-inline-start on :not(:first-child). Wrapping rows are the problem, because that rule leaves a stray line at the start of every new row, so switch to gap-based rules.
Multi-column and list items
The column-rule property has been Baseline widely available since March 2017 for multi-column layouts (MDN Web Docs).
MDN’s page also shows a flex list with gap: 12px and column-rule: 10px groove maroon, which belongs to the newer CSS Gaps module. The same page’s formal definition still says the property applies to multicol elements, so check the compatibility table before you use it on flex or grid (MDN, revised 5 October 2026).
Keep the rule narrower than the gap, as in that example, where the 12px gap leaves room for the 10px rule.
For navigation links and list items, a sibling selector such as li + li adds the line to every item except the first.
Tailwind and Bootstrap utilities
Both big CSS frameworks ship shortcuts for this job.
The utility-first Tailwind framework adds borders between children with divide-x and divide-y. Reversed order catches people out. When flex-row-reverse flips the items, add divide-x-reverse, or the border lands on the wrong side of each one (Tailwind documentation, v4.3).
Bootstrap goes the component route. Its .vr helper arrived in v5.1, measures 1px wide with a min-height of 1em, and stretches to the container height inside flex layouts (Bootstrap 5.3 documentation).
Should a CSS Divider Be Hidden From Screen Readers?
Hide it when the line is purely visual, and leave it exposed when it marks a real change of topic.
Published guidance disagrees, and the disagreement is about intent, not browser behavior.
| Source | Position on hr | Reasoning |
|---|---|---|
| MDN Web Docs | Implicit separator role, with presentation or none permitted | Native semantics stay unless overridden |
| Pennsylvania Keystone design system | Add aria-hidden=”true” | Treats the line as visual only |
| Wonderflow design system | Add aria-hidden when the line is not content-related | Same visual-only reading |
Keep the hr and its default role when the line marks a real break. For a visual-only line, draw it with a border or pseudo-element and add no node at all. Labeled dividers need nothing special, since the label stays readable text and the lines are pseudo-elements.
ARIA offers two ways to silence a leftover hr. According to MDN, aria-hidden="true" removes an element and all its children from the accessibility tree without hiding anything visually, while role="presentation" or none strips the semantics and keeps the content exposed.
For a childless hr the two end the same way. They differ on a wrapper, which is where mistakes happen.
MDN warns that aria-hidden is inherited, so it should not sit on a focusable element or on an ancestor of one. A toolbar wrapper that holds a divider and buttons would hide the buttons too.
If CSS already hides the divider with display: none, visibility: hidden, or the hidden attribute, skip the attribute. MDN says the element is out of the accessibility tree in all of those cases.
How to Make Dividers Responsive and Theme-Aware
See the Pen
Responsive SVG Arrow as Section divider by Domonkos Horvath (@domhorvath)
on CodePen.
Keep the sizing in relative units and the color in currentColor or a custom property. Media queries only come in when orientation or visibility has to change.
Percentage and rem widths scale with the container, so a 60% centered line stays proportional on a phone and on a wide monitor.
Orientation is trickier. A vertical divider between flex items turns into a stray sliver once the row wraps. Swap it for a horizontal rule with media queries, or hide the vertical one below the breakpoint.
Writing the stacked, horizontal version first and adding the vertical rule from a breakpoint up is the mobile-first order. It keeps the small-screen CSS short.
On color, currentColor lets the line follow the text color, and MDN lists currentcolor as the default for column-rule.
A common pattern goes one step further and exposes the color and spacing as custom properties, such as --divider-color and --divider-spacing, so one edit restyles every divider.
The prefers-color-scheme media feature has been Baseline widely available since January 2020 and reports light when the user has expressed no preference (MDN Web Docs). Override only the color token inside the dark query and every divider updates.
SVG dividers need one extra step. MDN says a prefers-color-scheme rule inside an SVG is meant for SVGs used as embedded files, through an img tag for example, rather than SVGs inlined in HTML, so inline waves should take their fill from a custom property.
Motion deserves a thought as well. MDN lists scaling and panning large objects as vestibular motion triggers, which covers animated waves that drift across the screen. The prefers-reduced-motion feature has been Baseline since January 2020, so stop or tone down the animation inside @media (prefers-reduced-motion: reduce).
When a CSS Divider Is the Wrong Choice
Skip the divider when spacing or the layout already separates the content, container edges included.
Volvo Cars’ design system documentation (@volvo-cars/css v3.1.0) lists the cases plainly, and the table pairs each one with a better move.
| Situation | Why a line fails | Better move |
|---|---|---|
| More margin would separate the blocks | The line only adds noise | Increase spacing |
| Inside a card, tile, or modal | The container already separates | Leave it out |
| Purely decorative accent | A divider signals structure, not style | Use a heading style or background |
| Covering a layout or alignment bug | The line hides the fault | Fix the layout |
The same documentation names where a divider does earn its place. Dense tables and settings panels, mostly, and groups of menu items where spacing alone does not show the boundary. Its example is a “Danger zone” block set apart from standard settings.
Layouts built on minimalist design principles lean on space and type for grouping, and add a line only when those fail.
Shape dividers need extra restraint. A wave or diagonal between every pair of sections looks like noise, not polish, so I’d use one at the hero edge and let plain spacing handle the rest.
The quickest test is to remove the line and look at the page. If the grouping is still clear, leave it out.
CSS Dividers Examples To Check Out
Benjamin’s Horizontal Delight
See the Pen
Cool Horizontal Divider Headings by Benjamin Knight (@benknight)
on CodePen.
Fresh Section Breaks by joshuar
See the Pen
Section Breaks by joshuar (@joshuar)
on CodePen.
Catchy SVG Page Separator by Alexandr Kazakov
See the Pen
SVG page separator by Alexandr Kazakov (@alexandr-kazakov)
on CodePen.
Color Burst with Slanted Sections by Adam Quinlan
See the Pen
Slanted Sections by Adam Quinlan (@quinlo)
on CodePen.
Rinatoptimus’s Circle Game
See the Pen
Divider with a circle by rinatoptimus (@rinatoptimus)
on CodePen.
HummixX’s Stylish Touch
See the Pen
Simple stylish divider with a little help of span. by HummixX (@HummixX)
on CodePen.
Get Creative with Email Designs
See the Pen
Email Design Conference CSS Divider by Eric Klemen (@emkmail2)
on CodePen.
Ema’s Boundary Breaker
See the Pen
Row Separator by Ema (@emared)
on CodePen.
Gregor’s Classic Horizontal
See the Pen
horizontal divider css by Gregor (@fenixmedia)
on CodePen.
Daniel Ilse’s Pseudo Genius
See the Pen
CSS Divider using :pseudo elements by Daniel Ilse (@dantilse)
on CodePen.
Starry Night Divider
See the Pen
Pure CSS Horizontal Divider With Star Icon by Isabel C (@isabelc)
on CodePen.
Getting Ziggy with Brandon
See the Pen
crooked section dividers by Brandon Kennedy (@brandonkennedy)
on CodePen.
Elegance in Lines
See the Pen
Fancy Horizontal Rules by szpakoli (@szpakoli)
on CodePen.
Ride the CSS Wave
See the Pen
Waves Content Divider Using CSS by CMDW (@cmdw)
on CodePen.
Milan’s Playground
See the Pen
Divider Experiments #1 by Milan (@Oddgson)
on CodePen.
Pixel Perfection
See the Pen
Pixel Dividers by C4rin3 (@c4rin3)
on CodePen.
Keep it Sleek with Pinline Headers
See the Pen
Header Pinlines by Robert Douglas (@redouglas)
on CodePen.
Skewed, Yet Perfect
See the Pen
Responsive Skewed Page Dividers (using CSS gradients) by chilli con code (@chilliconcode)
on CodePen.
Go Zigzag with Ninjaweb
See the Pen
Zigzag Divider by ninja (@Ninjaweb)
on CodePen.
Colors that Pop
See the Pen
Frame Patterns by Bram de Haan (@atelierbram)
on CodePen.
Divide and Rule with Christian
See the Pen
Resonsive Divider with Content by Christian Gorke (@WolfInStep)
on CodePen.
Scalloping Like a Pro
See the Pen
Responsive Scalloped Page Dividers (using CSS gradients) by chilli con code (@chilliconcode)
on CodePen.
Step Outside the Box
See the Pen
Non-rectangular Sections | CSS clip-path by Cagri Kizilkan (@cgrkzlkn)
on CodePen.
Daniel Padin’s Sleek Collection
See the Pen
Section Separators by Daniel Padin (@webd3v)
on CodePen.
One Element Wonder
See the Pen
Fancy title divider with one element by Temani Afif (@t_afif)
on CodePen.
Dashing in Rainbow
See the Pen
Rainbowy Dashed Divider by Simon Goellner (@simeydotme)
on CodePen.
Gone Fishing with Norbert
See the Pen
Fish logo in line by Norbert Papp (@partisan1991)
on CodePen.
The Forest Feels by Olly Hodgson
See the Pen
SVG forested separator bar by Olly Hodgson (@thinkdrastic)
on CodePen.
Medium Vibes with a Touch of Kindness
See the Pen
Accessible Medium Style Dividers by Zoë Bijl (@Moiety)
on CodePen.
Freecodecamp’s Finest
See the Pen
FreeCodeCamp style dividing line by Jeffrey Thomas (@2percentMilkFromCows)
on CodePen.
Triangulating with Mark
See the Pen
Triangle section Divider by Mark Sottek (@mark_sottek)
on CodePen.
Less is More with José Luis Antúnez
See the Pen
Animated <hr> by José Luis Antúnez (@jlantunez)
on CodePen.
Palette Play with Greg Douglas
See the Pen
Different Divider Examples by Greg Douglas (@xgad)
on CodePen.
The Diagonal Drama
See the Pen
Diagonal Split Screen by Chris Smith (@chris22smith)
on CodePen.
Caree’s Subtle Shadows
See the Pen
Box-Shadows as Page Dividers by Caree Youngman (@careecodes)
on CodePen.
CSS Dividers FAQ
Does the hr element need a closing tag?
No. A void element takes a start tag and must not have an end tag, according to MDN Web Docs, and hr is one. Write <hr> or <hr />; both are valid.
Can an hr go inside a select menu or a list?
Yes to both. MDN shows an hr between options inside a select element. Inside a list, wrap it in an li with role=”presentation” so the markup stays valid.
Can you animate a divider?
See the Pen
Pacman divider by C4rin3 (@c4rin3)
on CodePen.
Yes, with limits. MDN lists clip-path as animatable for basic shapes such as polygon(), but not for url() references. The column-rule-width property animates as a length, while border-image-source is discrete, so a gradient border flips instead of fading.
Should a divider be an image file or CSS?
CSS or inline SVG, in nearly every case. An image file adds a network request and cannot follow the page’s text color. A border can inherit currentColor, and so can an SVG fill.
Is an hr the same as a div with a border?
No. An hr carries an implicit separator role and the browser defaults for gray color, a 1px inset border and an auto inline margin (HTML Living Standard, 2026), since it is a thematic break. A div with a border has neither the meaning nor the defaults.
Building a Reusable Divider Class Set
A reusable set starts with one base rule and a token each for color, thickness and spacing. Each variant then overrides a single token instead of redefining the whole divider.
Build order matters, because each step depends on the one before it. Start with the hr reset using border-top, driven by the color token. Vertical, labeled and gradient variants come next on the same tokens, and shape dividers go last, with the fill read from the color token.
Tokens first means one prefers-color-scheme override recolors every divider at once, inline SVG waves included.
The trade-off is a longer stylesheet and one more naming layer to maintain. The base hr needs no motion rule or extra markup, while every shape divider adds both, so each one has to earn its place.
- What is Backend in Web Development? - September 13, 2026
- What is Frontend Development? - September 11, 2026
- How to Make a Button in Figma: Design Best Practices - September 10, 2026


