Accessibility pattern · Interaction and motion

Resizable panels

The separator between the panes is a focusable element with role=separator and a value: the share of the space the file list takes. Arrow keys resize it, Enter collapses and restores the file list, and buttons in the toolbar do the same for anyone who cannot drag.

WCAG criteria
7
Keyboard rules
5
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

Diwali launch

Files

launch-brief.md

Edited by Asha Rao, today

The Pune store opens on 14 November. We want 300 people on the mailing list by the end of the first week.

Every invitation goes out by email and WhatsApp, with the venue's step-free entrance on the map.

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
TabReaches the toolbar, the files, the separator and the preview in turn.
Arrow Left or Arrow RightOn the separator, narrows or widens the file list by 5 percent, within its limits.
Arrow Up or Arrow DownThe same when the panes are stacked and the separator runs across.
Home or EndGives the file list its smallest or its largest open size.
EnterCollapses the file list, or restores it to the size it had before.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
Tab reaches the separatorResize file list, separator, 38
Arrow Right widens the file list43
Enter collapses it0. File list collapsed
Enter restores it43. File list restored
The File list button is pressedFile list, button, collapsed. File list collapsed

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<div class="ap-window-splitter" data-ap-window-splitter>
  <div class="ap-window-splitter__bar">
    <button type="button" class="ap-window-splitter__toggle" data-ap-toggle aria-expanded="true" aria-controls="window-splitter-files">
      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M9 4v16"/><path class="ap-window-splitter__toggle-fill" d="M3.5 6.5a2 2 0 0 1 2-2H9v15H5.5a2 2 0 0 1-2-2Z"/></svg>
      <span>File list</span>
    </button>
    <h3 class="ap-window-splitter__project">Diwali launch</h3>
    <div class="ap-window-splitter__sizes" role="group" aria-label="File list size">
      <button type="button" class="ap-window-splitter__size" data-ap-size="-1" aria-label="Make the file list smaller" aria-disabled="false"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 12h12"/></svg></button>
      <button type="button" class="ap-window-splitter__size" data-ap-size="1" aria-label="Make the file list larger" aria-disabled="false"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 12h12M12 6v12"/></svg></button>
    </div>
  </div>

  <div class="ap-window-splitter__panes" data-ap-panes>
    <div class="ap-window-splitter__pane ap-window-splitter__pane--files" id="window-splitter-files">
      <h4 class="ap-window-splitter__pane-name" id="window-splitter-files-name">Files</h4>
      <ul class="ap-window-splitter__files" aria-labelledby="window-splitter-files-name">
          <li><button type="button" class="ap-window-splitter__file" data-ap-file="brief" aria-current="true"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8Z"/><path d="M14 3v5h5"/></svg><span translate="no">launch-brief.md</span></button></li>
          <li><button type="button" class="ap-window-splitter__file" data-ap-file="budget"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M4 10h16M4 15h16M10 4v16"/></svg><span translate="no">budget.csv</span></button></li>
          <li><button type="button" class="ap-window-splitter__file" data-ap-file="venue"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8Z"/><path d="M14 3v5h5"/></svg><span translate="no">venue-notes.txt</span></button></li>
          <li><button type="button" class="ap-window-splitter__file" data-ap-file="guests"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M4 10h16M4 15h16M10 4v16"/></svg><span translate="no">guest-list.csv</span></button></li>
      </ul>
    </div>

    <div class="ap-window-splitter__sep" role="separator" tabindex="0" aria-label="Resize file list" aria-controls="window-splitter-files" aria-orientation="vertical" aria-valuenow="38" aria-valuemin="0" aria-valuemax="70" data-min="20" data-ap-sep>
      <span class="ap-window-splitter__grip" aria-hidden="true"></span>
      <span class="ap-window-splitter__readout" aria-hidden="true" translate="no" data-ap-readout>38%</span>
    </div>

    <div class="ap-window-splitter__pane ap-window-splitter__pane--preview" role="region" aria-label="Preview" tabindex="0">
      <article class="ap-window-splitter__doc" data-ap-doc="brief">
        <h4 class="ap-window-splitter__doc-name" translate="no">launch-brief.md</h4>
        <p class="ap-window-splitter__meta">Edited by Asha Rao, today</p>
        <p>The Pune store opens on 14 November. We want 300 people on the mailing list by the end of the first week.</p>
        <p>Every invitation goes out by email and WhatsApp, with the venue's step-free entrance on the map.</p>
      </article>
      <article class="ap-window-splitter__doc" data-ap-doc="budget" hidden>
        <h4 class="ap-window-splitter__doc-name" translate="no">budget.csv</h4>
        <p class="ap-window-splitter__meta">Edited by Ravi Menon, yesterday</p>
        <dl class="ap-window-splitter__sums">
          <div><dt>Venue</dt><dd translate="no">₹1,20,000</dd></div>
          <div><dt>Printing</dt><dd translate="no">₹45,000</dd></div>
          <div><dt>Online ads</dt><dd translate="no">₹80,000</dd></div>
        </dl>
      </article>
      <article class="ap-window-splitter__doc" data-ap-doc="venue" hidden>
        <h4 class="ap-window-splitter__doc-name" translate="no">venue-notes.txt</h4>
        <p class="ap-window-splitter__meta">Edited by Meera Iyer, Monday</p>
        <p>Hall B holds 120 people. The lift is at the back, beside the café, and the hall has a hearing loop.</p>
      </article>
      <article class="ap-window-splitter__doc" data-ap-doc="guests" hidden>
        <h4 class="ap-window-splitter__doc-name" translate="no">guest-list.csv</h4>
        <p class="ap-window-splitter__meta">Edited by Asha Rao, last week</p>
        <p>86 people have replied so far. Twelve asked for seats near the front, and four for a sign language interpreter.</p>
      </article>
    </div>
  </div>
  <p class="ap-window-splitter__sr" role="status" data-ap-live></p>
</div>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.4.11 Non-text Contrast Level AA

    The separator's grip uses the control border color, at least 3:1 against both panes, so people can find what to drag.

  • 2.1.1 Keyboard Level A

    The arrow keys, Home, End and Enter do everything dragging does, and the toolbar buttons work from the keyboard too.

  • 2.4.7 Focus Visible Level AA

    The separator shows a focus ring and a size readout while it has keyboard focus.

  • 2.5.7 Dragging Movements Level AA

    Dragging has single-pointer alternatives: the File list button collapses and restores, the size buttons resize in steps, and a double click resets.

  • 2.5.8 Target Size (Minimum) Level AA

    The separator line is thin, but the area that takes the pointer is 25 pixels wide, and the toolbar buttons are 36 pixels square.

  • 4.1.2 Name, Role, Value Level A

    role=separator with aria-valuenow, aria-valuemin, aria-valuemax and aria-controls exposes the size and what it resizes; the File list button reports expanded or collapsed.

  • 4.1.3 Status Messages Level AA

    Collapsing and restoring are announced politely; the size itself is read from the separator's value, not repeated for every step.

Usage

When to use it

Use it

  • Two views people use together and size to suit the task: a file list and a preview, an inbox and a message, code and its output.
  • Tools used for long sessions, where people set the split once and keep it.

Use something else

  • Small screens: show one pane at a time, with a way back to the list.
  • A layout that never needs adjusting: a separator is one more thing to tab past.
  • Content that must keep fixed proportions, like a comparison: use columns.

Common failures

How it usually goes wrong

  • A bar only the mouse can move

    A plain div with a drag handler cannot be focused or named, so keyboard and screen reader users are stuck with whatever size it starts at.

  • A separator with no value

    Without aria-valuenow a screen reader says "separator" and nothing about where it is. The value here is the file list's share of the space.

  • Announcing every pixel

    A live region that reports each size change floods the speech queue during a drag. Only collapsing and restoring are announced; the size is read on focus.

  • A one-pixel target

    A hairline that has to be hit exactly is hard for anyone with a tremor. The line is thin, but the area that responds to the pointer is 25 pixels wide.

  • Dragging as the only way

    Many people cannot press, hold and move. The File list and size buttons resize with single clicks or taps.

  • Panes that vanish by accident

    Without limits a pane can be dragged to nothing and look broken. The file list stays between its smallest and largest sizes; collapsing it is a separate, reversible step.

Notes

Building it

  • The value is the file list's size as a percentage. aria-valuemin is 0 because the list can collapse; Home goes to the smallest open size instead, so it never hides the list by surprise.
  • aria-orientation describes the separator line, not the layout: side-by-side panes have a vertical separator that moves with Left and Right, stacked panes a horizontal one that moves with Up and Down.
  • Pointer capture keeps a drag going when the pointer slips off the thin separator, and touch-action: none stops a touch drag from scrolling the page instead.
  • Remember each person's size, for example in localStorage, so the layout comes back as they left it; this demo keeps it in the page only.
  • The WAI-ARIA Authoring Practices also suggest F6 to move between panes; Tab reaches both panes here, which is enough for two.

Sources: WAI-ARIA Authoring Practices: Window Splitter · WAI-ARIA 1.2: the separator role

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction