Coding standards¶
Eslint¶
It can show you errors on demand when writing your code - you have to install an editor plugin to use it. It can be used on the server-side in any test.
Rules are defined in files .eslintignore and eslint.config.mjs.
HTML Validation Rules¶
ESLint is configured with custom rules to catch HTML validation issues and ensure proper semantic markup. These rules help maintain accessibility and valid HTML structure.
Commands¶
# Check for validation issues
docker compose exec storefront pnpm lint
# Fix issues automatically (where possible)
docker compose exec storefront pnpm check--fix
# Check specific file
docker compose exec storefront npx eslint path/to/component.tsx
Button Content Validation¶
Buttons can only contain phrasing content elements, not block-level elements.
❌ Invalid Examples:
// Button containing div - INVALID
<button>
<div>Click me</div>
</button>
// Button containing paragraph - INVALID
<button>
<p>Submit form</p>
</button>
// Button containing heading - INVALID
<button>
<h3>Action Button</h3>
</button>
// Button containing list - INVALID
<button>
<ul>
<li>Option 1</li>
</ul>
</button>
// Button containing sectioning elements - INVALID
<button>
<section>Content</section>
</button>
<button>
<article>Content</article>
</button>
✅ Valid Examples:
// Button with text only - VALID
<button>Simple text button</button>
// Button containing span - VALID
<button>
<span>Text inside span</span>
</button>
// Button containing phrasing elements - VALID
<button>
<strong>Bold text</strong>
</button>
<button>
<em>Emphasized text</em>
</button>
<button>
<small>Small text</small>
</button>
<button>
<code>Code text</code>
</button>
// Button containing img/svg - VALID
<button>
<img alt="Icon" src="/icon.svg" />
Button with icon
</button>
<button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="..." />
</svg>
Button with SVG icon
</button>
// Button with multiple phrasing elements - VALID
<button>
<span>Click</span>
<strong>here</strong>
<em>now</em>
</button>
// Button with nested phrasing elements - VALID
<button>
<span>
<strong>Bold text in span</strong>
</span>
</button>
Interactive Element Nesting¶
Interactive elements cannot be nested inside other interactive elements.
❌ Invalid Examples:
// Button inside anchor - INVALID
<a href="/page">
<button>Click me</button>
</a>
// Anchor inside button - INVALID
<button>
<a href="/page">Link</a>
</button>
// Button inside button - INVALID
<button>
<button>Nested button</button>
</button>
✅ Valid Alternatives:
// Use role="button" for complex interactive content - VALID
<div
role="button"
tabIndex={0}
onClick={handleClick}
onKeyDown={handleKeyDown}
>
<div>Complex content structure</div>
<p>With multiple elements</p>
</div>
// Safari compatibility: Anchors with role="button" need explicit tabIndex
<a
href="/page"
role="button"
tabIndex={0} // ← Required for Safari keyboard accessibility
onClick={handleClick}
>
<span>Button-like link</span>
</a>
// Separate interactive elements - VALID
<div>
<a href="/page">Go to page</a>
<button onClick={handleAction}>Perform action</button>
</div>
// Use appropriate semantic elements - VALID
<a href="/page" className="button-style">
Link styled as button
</a>
<button onClick={handleClick}>
<span>Button with proper content</span>
</button>
HTML Phrasing vs Flow Content¶
Phrasing Content (Valid in buttons):
a, abbr, area, audio, b, bdi, bdo, br, button, canvas, cite, code, data, datalist, del, dfn, em, embed, i, iframe, img, input, ins, kbd, label, mark, math, meter, noscript, object, output, picture, progress, q, ruby, s, samp, script, select, small, span, strong, sub, sup, svg, template, textarea, time, u, var, video, wbr
Flow Content (Invalid in buttons):
div, p, h1-h6, ul, ol, li, section, article, aside, header, footer, nav, main, address, blockquote, details, dialog, fieldset, figure, form, table, etc.
Automatic Detection¶
Your ESLint setup automatically catches:
✅ jsx-a11y/interactive-supports-focus - Elements with role="button" that need tabIndex
✅ jsx-a11y/click-events-have-key-events - Click handlers without keyboard support
✅ jsx-a11y/no-static-element-interactions - Non-interactive elements with event handlers
Special Case - Safari Anchors:
Anchors with href and role="button" are not flagged by ESLint (they're normally focusable), but Safari requires explicit tabIndex={0} for proper keyboard access.
Why These Rules Matter¶
- HTML Validation: Ensures your markup follows HTML5 standards
- Accessibility: Screen readers expect semantic HTML structure
- Browser Compatibility: Prevents unexpected behavior across browsers (especially Safari)
- SEO: Search engines rely on proper HTML semantics
- Maintainability: Consistent structure makes code easier to understand
Prettier¶
You can format your code on save or be fired by a key shortcut - you have to install an editor plugin to use it.
Rules are defined in files .prettierignore and prettier.config.js.
Editorconfig¶
Adds coding standards into your IDE even if you don't have any plugins installed.
Rules are defined in file .editorconfig.