Svelte is one of the few frameworks that ships accessibility checks in the box. The compiler flags missing alt text, mismatched ARIA, and non-interactive elements with click handlers before you ever open a browser. That head start is real, but it stops at the markup you write at build time. The hard parts of Svelte accessibility, especially focus management across SvelteKit's client-side router, are entirely on you.
This guide covers four areas that matter in production: the a11y warnings the compiler actually emits, semantic markup in .svelte files, focus handling when SvelteKit navigates without a full page load, and patterns for accessible interactive components. The goal is WCAG 2.2 Level A and AA conformance, which is the baseline the European Accessibility Act enforces from 28 June 2025.
Read the compiler a11y warnings (don't suppress them)
Svelte runs an accessibility linter during compilation. When you write an <img> without alt, a <div on:click> with no keyboard handler, or aria-* attributes that don't match the element's role, you get a warning like a11y_no_static_element_interactions or a11y_missing_attribute right in your terminal. These map directly to WCAG criteria: missing alt fails 1.1.1 Non-text Content, and a click-only div fails 2.1.1 Keyboard.
The temptation is to silence them with <!-- svelte-ignore a11y_no_static_element_interactions -->. Resist it unless you've genuinely handled the issue another way. A click handler on a <div> is almost always a sign you should be using a <button> instead, which gives you keyboard activation, focusability, and the correct role for free:
- Wrong:
<div on:click={open}>Menu</div>triggers a warning and is unreachable by keyboard. - Right:
<button on:click={open}>Menu</button>is focusable, activates on Enter and Space, and announces as a button. - If you must keep a custom element interactive, add
role,tabindex="0", and anon:keydownhandler for Enter and Space, then the warning is legitimately resolved.
Treat the a11y warning list as a checklist, not noise. Note that Svelte 5 renamed these rules to snake_case (a11y_missing_attribute); older Svelte 3/4 projects use the hyphenated a11y-missing-attribute form in ignore comments. Run a build in CI and fail on these, the same way you would on a type error.
Write semantic markup, not styled divs
The compiler catches missing attributes, but it cannot tell you that your page has no <main>, no heading hierarchy, or six nested <div>s where a <nav> and a <ul> belong. Semantic HTML is what screen readers and landmark navigation rely on, and Svelte's template syntax is just HTML, so there is no framework-specific excuse to reach for divs.
- Use one
<h1>per route, then<h2>/<h3>in order. Don't skip levels for visual sizing; control size with CSS. - Wrap primary content in
<main>and put a skip link before it to satisfy 2.4.1 Bypass Blocks. - Use
<nav>,<header>,<footer>, and<button>/<a>for their real purposes. A link navigates; a button performs an action. - Label form fields with
<label for>and surface validation errors witharia-describedby, not just red borders, to meet 3.3.1 Error Identification.
In a SvelteKit layout, the skip link is a one-time addition that pays off on every page. Pair it with a tabindex="-1" target on <main> so the link can move focus there, which leads directly into the harder problem below. For the broader landmark and heading discipline, our accessibility checklist is a useful pass before you ship.
Manage focus on SvelteKit navigation
This is the single biggest gap in most Svelte accessibility work. When SvelteKit navigates client-side, it swaps the DOM but does not reload the page, so the browser's default behavior of moving focus to the top of a new document never fires. A keyboard or screen reader user clicks a link, the visible content changes, but their focus stays on the link that no longer exists or jumps to <body>. They get no announcement that anything happened, which undermines 2.4.3 Focus Order and leaves the experience broken.
SvelteKit gives you afterNavigate to hook into this. The reliable pattern is to move focus to a heading or the main landmark after each navigation, and to announce the new page title via a polite live region (4.1.3 Status Messages). In your root +layout.svelte:
- Import
afterNavigatefrom$app/navigationand, inside it, call.focus()on your<main>element (which needstabindex="-1"). - Skip the focus move on the very first load, where the browser already places focus correctly, so you only adjust on subsequent in-app navigations.
- Add a visually hidden
aria-live="polite"region and update its text with the new page's title so screen readers announce the route change. - Reset scroll position too, since focus and scroll are separate concerns and SvelteKit's default scroll handling won't help an assistive-tech user orient.
For modals and menus opened during a session, the rule is the same but tighter: move focus into the dialog on open, trap it while open, and return focus to the trigger on close. Keyboard reachability across all of this is what 2.1.1 hinges on, and our keyboard accessibility guide walks through the focus-trap and return-focus patterns in detail.
Build accessible interactive components
Custom dropdowns, tabs, accordions, and tooltips are where Svelte accessibility either holds up or falls apart. The compiler will nudge you on obvious ARIA mismatches, but it cannot verify that your aria-expanded actually tracks state or that arrow keys move between tab stops per the ARIA Authoring Practices. Wire ARIA attributes to Svelte's reactive state so they never drift:
- A disclosure button needs
aria-expanded={open}andaria-controlspointing at the panel'sid. Because it's reactive, the attribute updates the instant state changes. - Custom controls still need keyboard support: Escape to close, arrow keys to move within a composite widget, Enter/Space to activate.
- Respect
prefers-reduced-motionfor any transition; Svelte'stransition:directives run regardless, so gate them on the media query to avoid vestibular harm. - Check that interactive targets are at least 24x24 CSS pixels (2.5.8 Target Size Minimum, new in WCAG 2.2) and that focus indicators stay visible (2.4.11 Focus Not Obscured, also new in 2.2).
Color and contrast are component-level concerns too: text needs 4.5:1 (3:1 for large text, at least 18pt or 14pt bold), and UI components and focus rings need 3:1 against adjacent colors. Verify your design tokens with a contrast checker rather than eyeballing them. Automated tooling is fast but partial. Run a free accessibility scan to catch the machine-detectable issues across your routes, then test the focus and keyboard flows by hand, because no scanner can confirm that your SvelteKit navigation actually announces and moves focus correctly.