Accessibility and Best Practices in the New Frontend
Accessibility and Best Practices in the New Frontend
~8 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
Block 6 closes with a targeted accessibility pass over the four templates from chapter 53. No new feature is added here - every point is a small, concrete correction or addition to markup already shown, with reasoning rather than a bare assertion.
A semantic table instead of a card rebuild
Chapter 53 already anticipated this: history/index.phtml uses a real <table> with <caption class="sr-only">, <th scope="col"> per column header, and horizontal scrolling (overflow-x-auto) instead of a table visually rebuilt into cards via CSS. A screen reader user navigating the history with table navigation (e.g. NVDA/JAWS table mode) hears "column 3 of 4, points, +150" correctly announced - with a table visually rebuilt into cards and no scope attributes, that relationship is frequently lost.
aria-current for the active navigation link
Magento\Framework\View\Element\Html\Link\Current from chapter 52 already sets class="current" automatically when the link points to the current page - but that alone is only a visual, not a programmatically evaluable, marker. Hyvä's navigation template adds aria-current="page" based on exactly that same class:
<a href="<?= $escaper->escapeUrl($link->getHref()) ?>"
<?= $link->isCurrent() ? 'aria-current="page"' : '' ?>
class="<?= $link->isCurrent() ? 'font-semibold text-brand-accent' : 'text-gray-600' ?>">
<?= $escaper->escapeHtml($link->getLabel()) ?>
</a>That way not only a sighted user can tell visually which menu item is active - a screen reader also announces "My Points, current page". Without aria-current, the active marker stays a purely visual signal.
Form fields and error messages
- The filter
<select>incatalog/index.phtmlhas a<label>associated viafor/idinstead of only placeholder-like text - required, not an optional extra, otherwise a screen reader announces the field with no label at all. - The "not enough points" message in
catalog/view.phtmlcarriesrole="status": a screen reader reads it automatically as soon as it appears in the DOM (say, after a filter change), without focus needing to jump there - more appropriate for a pure information message thanrole="alert", which reads as more interruptive and should stay reserved for genuine errors. - Success/error messages after the redeem POST (chapter 50, via
messageManager) run through Hyvä's existingMagento_Theme::html/messages.phtml-role="alert"is already correctly set there for error messages, this module doesn't need to change or duplicate anything about that.
Focus order and the confirmation dialog
The Alpine dialog from chapter 53 (x-trap.noscroll, role="dialog", aria-modal="true", aria-labelledby) meets the four minimum requirements for an accessible modal: focus moves into the dialog on open and stays trapped there, escape closes it, the dialog is named via aria-labelledby pointing at its own heading, and aria-modal="true" tells assistive technology that the rest of the page is inert meanwhile. One point is deliberately left as an exercise in the chapter 53 version: focus should jump back to the original "Redeem now" button on close (escape or "Cancel") instead of landing at the top of the page - x-trap.noscroll doesn't handle that return trip automatically, only the opening.
<div x-data="{ confirmOpen: false }" x-id="['redeem-trigger']">
<button type="button" :id="$id('redeem-trigger')" @click="confirmOpen = true">...</button>
<div x-show="confirmOpen" x-trap.noscroll="confirmOpen"
@keydown.escape.window="confirmOpen = false; $nextTick(() => document.getElementById($id('redeem-trigger')).focus())">
...
</div>
</div>Color contrast on the status badges
Achtung: The tier badge (bg-amber-100 text-amber-800) and the points badge (bg-brand-accent/10 text-brand-accent-dim) from chapter 53 usually look sufficiently contrasty in a design mockup - but a transparent background like /10 depends on the underlying page background. Before a production rollout, always check against the actually rendered background with a contrast checker (e.g. the Chrome DevTools accessibility panel) that at least the WCAG 2.1 AA minimum ratio of 4.5:1 for body text is met - a plain assessment in a design tool isn't enough for that.
prefers-reduced-motion
The dialog deliberately uses only x-show with no x-transition animation - an abrupt show/hide is less problematic for users with vestibular disorders than a zoom or fade animation. If an animation were added, @media (prefers-reduced-motion: reduce) would be mandatory alongside it, either as a Tailwind motion-reduce: variant or as a dedicated CSS rule in the theme that sets transition-duration and animation-duration to near zero globally.
Checklist for block 6
- Every interactive surface (button, link, form field) has a visible
focus-visiblestate - already implemented across all four templates from chapter 53 viafocus-visible:outlineutility classes. - Every image and every meaningful icon (this block currently has no pure icon buttons without a text label - should that change in block 7/9 with widget or product type icons, each one needs a
<span class="sr-only">oraria-label). - Tables use
<th scope>instead of styled<td>cells. - Modal dialogs trap focus, close on escape, and return focus on close.
- Status messages use
role="status"for information,role="alert"only for genuine errors. - Color contrasts are checked against the rendered result, not the mockup.
Tipp: That wraps up block 6: from the first visible page (chapter 45) through the custom router (chapter 46), the block/view model decision (chapters 47/48), the reward catalog with redemption (chapters 49/50), to the complete, accessible frontend (chapters 51-54). Block 7 builds on top of it: a "My Points" widget for CMS pages and a custom Page Builder content type - both reuse the view models built in this block instead of duplicating them.