Platform Guides

Framer Accessibility Guide: Components, Focus, Traps

Framer Accessibility Guide: Components, Focus, Traps

Framer logo against a gradient fade

Framer can ship accessible sites—if you set structure, manage focus, and choose components wisely. This guide shows practical patterns for headings, landmarks, forms, dialogs, and navigation, plus how to avoid common traps. Then add Adjustable to give visitors contrast and text-size controls that lift engagement and conversions.

Framer basics: structure first

  • One H1 per page; use a logical H2/H3 outline in Rich Text.

  • Wrap sections with landmarks: Header, Nav, Main, Footer.

  • Use real elements for actions and navigation: <button> for actions, <a href="…"> for navigation.

Skip link (drop into a Code component near the top)

<a href="#main" style="position:absolute;left:-9999px" onfocus="this.style.left='8px'">Skip to content</a>
<main id="main"></main>

Tip: In Framer, you can add a Code component for the skip link and target the main layer by giving it the id="main" in the layer’s properties.

Focus management: make it visible and predictable

  • Ensure a visible focus outline for links, buttons, and form controls.

  • Don’t remove outlines globally. Style them instead.

CSS you can add via a Code component

:root { --focus-ring: 2px solid #111318; }
:where(a, button, [role="button"], input, select, textarea):focus-visible {
  outline: var(--focus-ring);
  outline-offset: 3px;
}
  • Keep Tab order in visual sequence. Avoid layering focusable elements off-screen.

  • Test: Tab through the page; you should always see where you are.

Navigation & menus

  • Use real links for menu items; expand/collapse submenus with buttons.

  • When a menu opens, the next Tab should move inside the menu; Esc closes it and returns focus to the trigger.

  • Don’t rely on hover-only menus; mirror hover styles on :focus-visible.

Menu button pattern (React/Code component)

import * as React from "react";

export default function MenuButton() {
  const [open, setOpen] = React.useState(false);
  const btnRef = React.useRef<HTMLButtonElement>(null);
  const menuRef = React.useRef<HTMLDivElement>(null);

  React.useEffect(() => {
    function onKey(e: KeyboardEvent) {
      if (e.key === "Escape" && open) { setOpen(false); btnRef.current?.focus(); }
    }
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [open]);

  return (
    <div className="menu">
      <button ref={btnRef} aria-expanded={open} aria-controls="mainmenu" onClick={() => setOpen(!open)}>
        Menu
      </button>
      {open && (
        <div id="mainmenu" role="menu" ref={menuRef}>
          <a role="menuitem" href="/pricing">Pricing</a>
          <a role="menuitem" href="/about">About</a>
          <a role="menuitem" href="/contact">Contact</a>
        </div>
      )}
    </div>
  );
}

Dialogs, drawers, and overlays (avoid focus traps)

  • A dialog moves focus inside when opened, traps focus while open, and returns it to the trigger on close.

  • Add role="dialog" and aria-modal="true"; give the dialog a visible title linked via aria-labelledby.

Dialog pattern (React/Code component)

import * as React from "react";

export default function A11yDialog({ title = "Quick view", children }: any) {
  const [open, setOpen] = React.useState(false);
  const btnRef = React.useRef<HTMLButtonElement>(null);
  const dlgRef = React.useRef<HTMLDivElement>(null);

  React.useEffect(() => {
    if (!open) return;
    const first = dlgRef.current?.querySelector<HTMLElement>('[tabindex],a,button,input,select,textarea');
    first?.focus();
    function onKey(e: KeyboardEvent) {
      if (e.key === "Escape") { setOpen(false); btnRef.current?.focus(); }
    }
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [open]);

  return (
    <>
      <button ref={btnRef} onClick={() => setOpen(true)}>Open dialog</button>
      {open && (
        <div role="dialog" aria-modal="true" aria-labelledby="dlg-title" ref={dlgRef}>
          <h2 id="dlg-title">{title}</h2>
          {children}
          <button onClick={() => { setOpen(false); btnRef.current?.focus(); }}>Close</button>
        </div>
      )}
    </>
  );
}

In Framer, place this Code component on the page, then style the overlay and container with your design tokens.

Forms in Framer: labels, errors, and validation

  • Every input needs a visible label linked with for/id.

  • Place instructions before fields; mark required status clearly.

  • Validate on blur and submit (not per keystroke).

  • Announce errors with a polite live region and link summary items back to fields.

Email field pattern

<label for="email">Email address</label>
<p id="email-hint">We’ll send your receipt here.</p>
<input id="email" name="email" type="email" autocomplete="email" aria-describedby="email-hint">

Error snippet

<div id="form-errors" aria-live="polite"></div>
<span id="email-error" hidden>Enter a valid email like name@domain.com</span>
<input id="email" aria-describedby="email-error" aria-invalid="true">

Common Framer traps (and fixes)

  • Divs as buttons/links → Replace with <button> or <a>; add keyboard handlers.

  • Hidden focus → Don’t remove outlines; style :focus-visible with a high-contrast ring.

  • Off-canvas layers still tabbable → set inert or tabIndex="-1" when hidden.

  • Auto-play motion → respect prefers-reduced-motion; give users a pause.

  • Hover-only effects → mirror on :focus-visible so keyboard users get the same cues.

Quick testing routine (10–12 minutes)

  1. Keyboard pass (5 min): Tab through the page—can you operate nav, menus, dialogs, and forms? Is focus always visible?

  2. Screen reader skim (3–4 min): Title, headings list, landmarks, links list; confirm labels and error messages.

  3. Mobile spot check (2–3 min): Tap targets, reflow at 320px, zoom allowed; overlays don’t block focus.

Copy-paste checklist (Markdown)


How Adjustable helps

Once your components behave accessibly, Adjustable boosts readability and comfort across Framer pages:

  • Contrast and text-size controls help visitors read and act with ease.

  • Reading aids and motion preferences reduce fatigue in long sessions.

  • Simple install, immediate UX uplift—perfect for Marketing Managers and Website Owners.

Try Adjustable

FAQs

Do Framer components have accessibility built in?
They’re a strong start, but you must set structure and focus patterns correctly—especially for menus, dialogs, and forms.

Can I run automated checks on a Framer site?
Yes—run a scanner on live pages and pair it with the keyboard/screen reader checks above.

What’s the quickest win?
Add a skip link, enforce visible focus, and fix div-as-button anti-patterns on your main templates.

Next steps

  • Add a skip link and visible focus styles today.

  • Refactor nav/menu and dialog components using the patterns above.

  • QA your top pages with the checklist and a keyboard pass.

  • Install Adjustable to improve readability while you iterate.

Share:

Facebook logo
LinkedIn logo
Instagram logo
X logo
Email icon