Accessibility Widget: Storefront Integration
Accessibility widget for Sylius - Twig hooks, skip link target, theming via CSS custom properties, theme compatibility
Accessibility Widget: Storefront Integration
Live Demo: Try this and other plugins live — no installation, no risk: demo.markus-michalski.net
Twig hooks
The plugin registers itself through these hooks, no core template is overridden:
| Hook | Purpose |
|---|---|
sylius_shop.base#stylesheets |
Loads accessibility.css; with at least one active tool also accessibility-early.js (synchronous, before the first render) and, with the colour filter active, an inline <style> with the filter rules |
sylius_shop.base#javascripts |
Loads accessibility.js (defer), only when at least one tool is active |
sylius_shop.base.header |
Skip link (priority 1000, at the very front) and the jump target (priority 50) |
sylius_shop.base.offcanvas |
The floating widget itself (priority -100), outside the page wrapper |
A theme that replaces these hook slots has to render the plugin's templates itself, otherwise nothing appears.
Skip link and jump target
The skip link is a link <a class="mmd-a11y-skip-link" href="#mmd-a11y-main"> with the text "Skip to main content" (DE: "Zum Hauptinhalt springen"). It sits off-screen via CSS and appears only when it receives keyboard focus.
The shop's base template has no <main> and no content ID. The plugin therefore prints its own empty, focusable <div id="mmd-a11y-main" tabindex="-1" role="region"> with an aria-label ("Main content") in the hook sylius_shop.base.header, between the navigation (priority 100) and the flash messages (priority 0), so that a validation message or "added to cart" message is not skipped after the jump. Link and target appear only when the skip link setting is switched on for the channel.
If your theme places the content elsewhere, override the plugin's template following the usual Symfony pattern at templates/bundles/MmdSyliusAccessibilityPlugin/shop/skip_target.html.twig (original: templates/shop/skip_target.html.twig in the plugin).
Theming
The effects are plain classes on <html>; colours and spacing can be overridden from your theme through CSS custom properties:
:root {
--mmd-a11y-primary: #0b6b3a;
--mmd-a11y-primary-dark: #08522c;
--mmd-a11y-offset-x: 24px;
--mmd-a11y-offset-bottom: 90px;
--mmd-a11y-z-index: 1020;
}
More properties in the stylesheet: --mmd-a11y-bg, --mmd-a11y-text, --mmd-a11y-text-light, --mmd-a11y-border, --mmd-a11y-control-border, --mmd-a11y-focus, --mmd-a11y-hover, --mmd-a11y-size and --mmd-a11y-panel-width.
| Setting | Classes on <html> |
|---|---|
| Font size | mmd-a11y-font-small, mmd-a11y-font-large |
| Contrast | mmd-a11y-contrast-high, mmd-a11y-contrast-inverted |
| Colour filter | mmd-a11y-filter-protanopia, -deuteranopia, -tritanopia |
| Blue light | mmd-a11y-blue-light |
| Reduce animations | mmd-a11y-reduce-motion |
localStorage key: mmd-accessibility-settings. A headless storefront can reuse the same classes as a convention (see Shop API).
The colour filters deliberately sit on
<html>, not on a wrapper element: a filter on a wrapper would become the containing block of everyposition: fixedelement (sticky header, banners) and break their placement.
Behaviour in detail
- No flash on load. A small synchronous script in the
<head>(accessibility-early.js, an external file so a strict CSP needs no exception for it) puts the saved settings on<html>before the first render. The widget script builds on it. - Font size is relative. "Small" and "Large" set the font size on
<html>to 87.5 % and 125 % of the visitor's browser setting, they never replace it with a fixed pixel size. - Contrast. "High" applies
filter: contrast(1.35)to<html>, "Inverted" appliesinvert(1) hue-rotate(180deg). Images and videos get a counter-filter so photos keep their natural look. - Blue light filter. A semi-transparent yellow-orange layer (12 % opacity) is placed over the page and does not intercept clicks.
- Reduce animations. Animation and transition durations are set to practically zero and smooth scrolling is switched off. If the operating system already requests reduced motion, the same rule applies without any widget choice.
- The panel is a non-modal dialog. The page behind it stays usable, so there is no focus trap. It closes on Esc, on a click outside and when keyboard focus leaves it. "Reset all settings" puts every tool back to its normal state.
- The colour filters are an approximation. They consist of three fixed 3×3 colour matrices (SVG
feColorMatrix) for protanopia, deuteranopia and tritanopia. This is not a Brettel algorithm and not a measurement; how closely a filter matches or helps with a specific colour-vision deficiency is not guaranteed by the plugin. The filter rules are printed inline becauseurl('#id')in an external stylesheet cannot reach the inline SVG filters; a strict Content Security Policy needs a nonce or hash for this<style>. - Asset URLs are built without
asset(): while a Sylius theme is active it rewrites bundle assets to/_themes/..., which exists only aftersylius:theme:assets:install. The file's modification time serves as the cache-busting version. - Disabled tools. A visitor's saved choice for a tool the channel no longer offers is ignored.