Accessibility and RTL support

What Foundation's plugins already do for keyboard and screen reader users, what you must add yourself, and how to build a right-to-left layout that holds.

What the plugins handle

ComponentProvidedYour responsibility
RevealFocus trap, Escape, returns focus to the triggerA labelled heading, and not opening it on page load
Accordionaria-expanded and aria-controls on the titleMeaningful title text, sensible default open item
Tabsaria-selected, arrow-key navigationPanel tabindex="0" if it has no focusable content
Off-canvasFocus containment while open, close on EscapeA close button with an accessible name
Dropdownaria-expanded, aria-haspopupDistinguishing a menu of actions from a list of links
Tooltiparia-describedby on the triggerNever the only source of essential information
Abide (validation)Error keys, focus on the first invalid fieldText that explains what to do, not just that it is wrong
<div class="reveal" id="confirm" data-reveal
     aria-labelledby="confirmTitle" aria-describedby="confirmBody">
  <h2 class="h5" id="confirmTitle">Remove the integration?</h2>
  <p id="confirmBody">This deletes the stored token. You will need to reconnect.</p>
  <button class="button secondary" data-close type="button">Cancel</button>
  <button class="button alert" type="button">Remove integration</button>
</div>

<button class="button" type="button" aria-haspopup="dialog" data-open="confirm">
  Remove integration
</button>
⚠️
A reveal that opens automatically on page load traps the keyboard before the user has read anything. Open dialogs only in response to a user action, and always give them a heading that describes the decision.

What you still owe

<a class="show-on-focus button small" href="#main">Skip to content</a>

<header role="banner">
  <nav aria-label="Primary">…</nav>
</header>

<main id="main" tabindex="-1">…</main>

<!-- Icon-only control: needs a name -->
<button class="close-button" type="button" aria-label="Close panel" data-close>
  <span aria-hidden="true">&times;</span>
</button>

<!-- Live region for status that appears asynchronously -->
<div class="callout success" role="status" aria-live="polite" id="status"></div>
/* Foundation's show-for-sr hides content visually but keeps it announced. */
.show-on-focus {
  position: absolute;
  top: -100px;
  left: 0;
  z-index: 200;
  &:focus { top: 0; }
}

/* Never remove focus outlines globally. Style them instead. */
:focus-visible {
  outline: 2px solid get-color(primary);
  outline-offset: 2px;
}

/* Foundation's own reduced-motion guard covers Motion UI; be explicit for your JS. */
@media (prefers-reduced-motion: reduce) {
  .reveal { animation: none !important; }
}
  • show-for-sr is the Foundation utility for text only a screen reader should hear. It is the right way to name the current page in pagination.
  • Foundation adds landmarks for some components but not for your page structure. <header>, <nav> and <main> are still your job.
  • A button with only a glyph has no accessible name. aria-label on the button plus aria-hidden="true" on the glyph is the reliable pattern.
  • Focus order follows the DOM. If a layout visually reorders content with grid-margin and order classes, check that the tab order still makes sense.

Right-to-left layouts

/* settings.scss — one variable switches the whole framework.
   Foundation mirrors the grid, menus, buttons and callouts in the compiled CSS. */
$global-text-direction: rtl;

/* Foundation reads this and converts its own left/right declarations to
   logical ones. Note the output roughly doubles for directional rules. */
$lang-direction: rtl;
<html lang="ar" dir="rtl">
  <!-- Foundation's compiled RTL stylesheet applies to this whole document -->
</html>

<!-- A single LTR island inside an RTL page -->
<div dir="ltr" class="invoice-number" lang="en">INV-1042</div>
LTR-eraDirection-neutralReason
float: leftfloat: inline-startFollows the writing direction
margin-leftmargin-inline-startSame
padding-rightpadding-inline-endSame
text-align: lefttext-align: startSame
border-leftborder-inline-startSame
A background arrow iconA mirrored icon or transform: scaleX(-1)Icons with direction must mirror

The two things that break most often in an RTL build are hard-coded pixel offsets in your own CSS and directional icons — chevrons, arrows, progress indicators. Set $global-text-direction: rtl, rebuild, then read through your component partials looking for any remaining left or right in a property name.

FAQ

Do I need two builds for LTR and RTL?
With the Sass global you compile one or the other. To ship both, run the compiler twice with the variable flipped and a different output path, then choose the stylesheet based on the document language. A single build cannot serve both.
Is Foundation's focus management enough for a modal?
It traps focus and restores it to the trigger, which covers the two common failures. It does not give the dialog an accessible name — that comes from your aria-labelledby pointing at a real heading inside the dialog.

Motion UI and animation Building a complete page with Foundation

Last refreshed 2026-09-18.