← Blog
4 Oct 2026HTMLCSSaccessibilityresponsive designflexbox

Vertical Divider HTML: Flexbox, CSS Borders, and Accessible Separators

Create a vertical divider in HTML using a semantic hr or a decorative CSS border. Follow responsive flexbox and grid examples, accessibility checks, and fixes.

Vertical Divider HTML: Flexbox, CSS Borders, and Accessible Separators

TL;DR

For a vertical divider in HTML, decide what the boundary means before drawing it. Use a styled <hr> only for a meaningful break between content groups; give its vertical version aria-orientation="vertical". For an ordinary dashboard column edge, use a CSS border on the existing panel instead. Let flexbox or grid stretch the line with the content, and switch to a horizontal rule or border when the layout stacks.

A line between panels can look correct at one viewport and still be wrong for the document. A border does not announce a new section. A semantic rule should not masquerade as a draggable control. And a divider set to height: 100vh usually has little to do with the height of the content around it. In this guide, we will build a flexbox-based separator, adapt it to a stacked layout, then use the leaner border pattern where semantics do not call for a rule.

Before you start: Have two actual content regions with meaningful headings, a stylesheet you can edit, and a browser for resizing and keyboard checks. The examples use plain HTML and CSS, with no framework installation or JavaScript. Put the HTML inside your existing page body and the CSS in its loaded stylesheet. The 48rem breakpoint is an example: change it when your content, not a device category, needs a different layout. The snippets assume a conventional horizontal writing mode; check physical placement if your interface uses another writing mode.

Table of contents

1. Choose what the divider represents

First, ask whether the two sides express a change in subject, or whether they simply sit next to each other. HTML’s <hr> represents a thematic break and already has the implicit separator role. It is not a generic line-drawing element. MDN documents the meaning and implicit role of <hr>.

In a reporting screen, a usage overview and a separate incident review queue might reasonably warrant a thematic break. A sidebar beside the work area, two fields in the same form, or an icon beside its label usually does not. In those cases, a border on an existing region preserves the visual design without inventing a structural announcement. If headings and spacing make the grouping obvious, you may need no line at all.

What if users must drag the boundary? Then the requirement has changed. A focusable, moveable window splitter has keyboard interaction, value, and accessible-name requirements; a static <hr> or border does not meet them. The W3C window splitter pattern covers that separate interaction. For a complete product-level implementation discussion, see our accessible splitter UI guide.

Visual comparison of a semantic rule, decorative panel border, and interactive resizing handle

A practical decision for common screens:

  • Dashboard cards or related form columns: prefer a decorative border, or rely on space and headings.
  • Separate content topics shown side by side: consider <hr> if the break is meaningful in reading order too.
  • Navigation beside a workspace: use structural HTML for navigation and main content; a boundary line is usually decoration.
  • User-resizable application panes: implement a genuine splitter, not a line with a pointer cursor.

Check: can you explain the boundary without mentioning its appearance? If not, start without separator semantics. That choice is more important than whether you eventually use flexbox or grid.

2. Put the semantic rule between the right elements

Use this example only if the two sections really do warrant a thematic break. The source order is overview, separator, then review queue. The headings give each region an identity even if the CSS fails or the rule is difficult to see.

<div class="report-pair">
  <section aria-labelledby="usage-title">
    <h2 id="usage-title">Usage overview</h2>
    <p>Activity and trends for this reporting period.</p>
  </section>

  <hr class="report-pair__vertical" aria-orientation="vertical">
  <hr class="report-pair__horizontal">

  <section aria-labelledby="incidents-title">
    <h2 id="incidents-title">Incident review queue</h2>
    <p>Cases that need a separate follow-up decision.</p>
  </section>
</div>

There are two rules because CSS alone changes appearance, not an ARIA attribute. We display the vertical rule only in the two-column layout, and the horizontal rule only when the sections stack. The second <hr> retains the separator role’s default horizontal orientation. This keeps the announced orientation aligned with the visible line without introducing JavaScript. MDN’s separator-role reference explains both that default and the distinction between static and focusable separators.

Do not add role="separator" to the <hr>: it already has that role. Do not add tabindex="0", aria-valuenow, or a drag label to this static boundary. The headings carry the information about the regions; the rule does not need to become a control or replace their labels. If the two breaks would be misleading when styles are unavailable, use a border instead or decide whether the content truly needs a thematic break. With styles disabled, both native <hr> elements appear horizontally, which is a trade-off of this CSS-only pattern.

Expected result: the document makes sense in DOM order, independently of its columns. If you cannot distinguish the topics without the line, improve the headings or grouping before styling the separator.

3. Make the rule stretch with flexbox, then stack it

Start from the narrow layout, where the two sections follow one another. At a width that comfortably fits both, switch to a flex row. The desktop rule becomes its own zero-basis flex item with a one-pixel border; align-self: stretch lets it span the row’s cross-axis height. This follows MDN’s flex alignment explanation, rather than relying on a guessed fixed height.

.report-pair {
  --rule-color: #64748b;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

.report-pair > hr {
  margin: 0;
  border: 0;
}

.report-pair__vertical {
  display: none;
}

.report-pair__horizontal {
  width: 100%;
  border-block-start: 1px solid var(--rule-color) !important;
}

@media (min-width: 48rem) {
  .report-pair {
    flex-direction: row;
    align-items: stretch;
    gap: 1.5rem;
  }

  .report-pair > section {
    flex: 1 1 0;
    min-width: 0;
  }

  .report-pair__horizontal {
    display: none;
  }

  .report-pair__vertical {
    display: block;
    flex: 0 0 0;
    align-self: stretch;
    border-inline-start: 1px solid var(--rule-color) !important;
  }
}

Important styling correction: Avoid the !important declarations if you can control selector specificity. The universal .report-pair > hr { border: 0; } above has higher specificity than the bare class selectors, so those two border declarations need either matching specificity or the override shown here. A cleaner production version uses .report-pair > .report-pair__horizontal and .report-pair > .report-pair__vertical for the border rules, removing both !importants. Make that change when merging these styles into your design system.

A vertical divider stretches between unequal-height flex panels, while a mobile layout stacks the panels

The narrow view should show one horizontal rule. At 48rem and above, the same DOM order appears as two columns with one vertical rule reaching the row’s height. The flex: 0 0 0 keeps the rule from taking a share of the available content width; the border itself supplies the visible pixel. min-width: 0 lets each flex item shrink rather than insisting on its automatic content minimum. Wide tables, long identifiers, and charts can still overflow internally and need their own wrapping or scrolling policy. See MDN’s min-width reference for the automatic minimum behavior.

Check: put several extra paragraphs in one section and resize across the breakpoint. The desktop line should follow the taller section, while the mobile line stays between stacked sections. If the vertical line is short, inspect a parent rule such as align-items: center or a fixed height on the <hr>. If a column spills sideways, look for an unbreakable child before making the separator wider. The logical borders used here map to the inline or block start edge according to writing mode; MDN explains that mapping.

A grid can do the same job when the rest of the screen already uses grid tracks. For the same markup, the wide layout can use grid-template-columns: minmax(0, 1fr) 1px minmax(0, 1fr) with the vertical rule in the center track, while the narrow layout uses one column. Explicitly hide the unused rule at each width and inspect the grid placement: if both rules remain displayed in a three-track grid, one can create an unexpected extra row or track. Flexbox is convenient here because the two panels simply share a row; grid is convenient when track placement is already part of the layout. MDN’s grid layout guide describes fixed and flexible tracks.

4. Replace the rule with a border for everyday panels

For most application chrome, do not insert a dedicated separator element. Here is a pair of related settings panels where the line is purely decorative. We put the border on the second section, so the HTML remains about the content rather than a drawn object.

<div class="settings-pair">
  <section>
    <h2>Profile</h2>
    <p>Public name and account details.</p>
  </section>
  <section class="settings-pair__second">
    <h2>Preferences</h2>
    <p>Display and notification choices.</p>
  </section>
</div>
.settings-pair {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.settings-pair__second {
  border-block-start: 1px solid #64748b;
  padding-block-start: 1.5rem;
}

@media (min-width: 48rem) {
  .settings-pair {
    flex-direction: row;
    align-items: stretch;
  }

  .settings-pair > section {
    flex: 1 1 0;
    min-width: 0;
  }

  .settings-pair__second {
    border-block-start: 0;
    padding-block-start: 0;
    border-inline-start: 1px solid #64748b;
    padding-inline-start: 1.5rem;
  }
}

Because flex items stretch in the cross axis, this second panel normally reaches the row’s height and its border reaches with it. If you change the row to align-items: flex-start, the line will follow only the height of its own section. Decide which visual behavior you actually need. A border requires no role, aria-orientation, or extra keyboard stop. Use real headings and, in a form, keep field labels and grouping semantics intact; a one-pixel line is not an alternative to those relationships.

For an actual fixed sidebar beside a scrollable work area, our Application Layout example illustrates the larger shell context. It uses a bordered left region rather than claiming that an application edge is a thematic break. The short video below demonstrates the CSS line technique; treat it as a visual companion, and use the semantics and responsive checks here to decide whether that technique fits your page.

Expected result: a border marks the panel edge at desktop size and switches to the block-start edge once the panels stack. If a vertical remnant survives on mobile, ensure the desktop-only border-inline-start is confined to the wide media query, as in this snippet.

5. Check the result with real content and accessibility settings

A divider is complete only when the information survives beyond the design mockup. We run the following tests on the actual page:

  1. Width and zoom: shrink the browser through the breakpoint and try increased zoom. Look for clipped headings, sideways scrolling, and a rule left in the wrong orientation. MDN recommends setting breakpoints where content needs them, not by guessing popular device widths.
  2. Uneven content: add a long validation message or another paragraph to one region. Check whether the rule spans the intended flex row and whether a border follows its owner. Do not use 100vh just to make a card divider seem tall: the viewport and the card are different sizing contexts.
  3. Keyboard and reading order: Tab through the page. Neither static <hr> nor a border should receive keyboard focus. Read through both sections with a screen reader in a supported browser: check headings, order, and that only the visible rule is exposed at each layout size. A reading cursor may encounter a static separator even though Tab does not.
  4. Contrast and forced colors: make sure headings and grouping remain understandable if the line is faint or missing. Test your supported high-contrast configuration; browsers can override border colors and suppress non-URL background images under forced-colors mode. A real border is preferable to a background-only gradient when the line matters visually.

If something fails, inspect the DOM and computed styles before changing ARIA. An empty <div role="separator"> does not fix a missing heading, and rotating an <hr> with transform does not make it participate in the layout as a correctly sized, stretched divider. Likewise, gap creates space, not a meaningful boundary. Test the element’s size and reading order, not just its angle in a screenshot.

FAQ

Can I use an <hr> as a vertical divider in HTML?

Yes, when it represents a thematic break. Style it to make a vertical line in a flex or grid row and set aria-orientation="vertical" on the rule that is displayed vertically. If it is only decoration, use a CSS border instead. An <hr> does not need an explicit role="separator".

Why does my vertical divider not fill the panel height?

Usually its height was set relative to the wrong box, or the container stopped stretching items. In the flex example, align-items: stretch and align-self: stretch make the rule follow the row’s height. In the border example, the border follows the second panel. Check unequal content heights and any overridden alignment before setting a fixed height.

Is a vertical separator the same as a splitter?

No. A separator can be a static structural boundary; a splitter is a movable widget that changes pane sizes. If resizing is the goal, follow an interactive splitter pattern and consider DOM Studio’s Splitter Panel reference rather than putting drag behavior on a static rule.

Apply it to one real screen

Pick the smallest screen in your product that actually needs this boundary. Start with headings and normal source order, then select the semantic flex rule or decorative border that reflects the content. Resize with uneven content and check keyboard reading before you reuse it elsewhere. For a complete application shell, continue with the DOM Studio Application Layout block; if a user needs to move the line, switch to the Splitter Panel reference instead.

Sources

Recommended Reads