Skill v1.0.1
currentAutomated scan100/1003 files
version: "1.0.1" name: e11y-fix description: Reference guide for fixing axe-core/WCAG errors in Phoenix LiveView - maps common violations to Phoenix-specific fixes
Excessibility Fix - axe-core Error Reference
Quick reference for fixing axe-core/WCAG errors in Phoenix LiveView applications.
Debugging Workflow
- Run
mix excessibilityto see axe-core errors - Use
html_snapshot(view)to capture the problematic state - Read snapshot file to see exact HTML
- Apply fix from reference below
- Re-run
mix excessibilityto verify
Quick Reference
| axe-core Error | WCAG Rule | Phoenix Fix | |
|---|---|---|---|
| Form input without label | 1.3.1 | Add <.label> or aria-label | |
| Missing alt text | 1.1.1 | Add alt attribute to <img> | |
| Low contrast | 1.4.3 | Adjust Tailwind colors | |
| Missing lang | 3.1.1 | Add lang="en" to <html> | |
| Empty link | 2.4.4 | Add link text or aria-label | |
| Missing heading structure | 1.3.1 | Use proper <h1>-<h6> hierarchy | |
| No skip link | 2.4.1 | Add "Skip to content" link |
Common Fixes by Category
Forms
Input without label
Error: "Form element does not have a label"
<!-- Bad --><input type="text" name="email" /><!-- Good: Using Phoenix form helpers --><.input field={@form[:email]} type="email" label="Email address" /><!-- Good: Manual label --><label for="email">Email address</label><input type="text" name="email" id="email" /><!-- Good: aria-label for icon-only --><input type="search" aria-label="Search" />
Form without submit button
Note: LiveView forms using phx-submit often don't need a visible submit button. axe-core may flag this. You can:
- Add a submit button (even if styled invisibly)
- Configure axe-core rules to ignore this rule for specific forms
- Use
aria-labelon the form to explain the interaction
<!-- phx-submit form with hidden submit --><form phx-submit="save"><.input field={@form[:name]} type="text" label="Name" /><button type="submit" class="sr-only">Submit</button></form>
Required fields
<.inputfield={@form[:email]}type="email"label="Email"required/><!-- Renders with aria-required="true" -->
Error messages
<.inputfield={@form[:email]}type="email"label="Email"errors={@form[:email].errors}/><!-- Errors should be associated via aria-describedby -->
Images
Missing alt text
Error: "Image missing alt attribute"
<!-- Bad --><img src={@avatar_url} /><!-- Good: Meaningful alt --><img src={@avatar_url} alt={"Profile picture of #{@user.name}"} /><!-- Good: Decorative image (empty alt) --><img src="/images/decorative-line.png" alt="" /><!-- Good: Icon with adjacent text --><span><img src="/icons/star.svg" alt="" />Favorites</span>
Links
Empty link
Error: "Link text is empty"
<!-- Bad: Icon-only link --><.link navigate={~p"/settings"}><.icon name="hero-cog-6-tooth" /></.link><!-- Good: Add aria-label --><.link navigate={~p"/settings"} aria-label="Settings"><.icon name="hero-cog-6-tooth" /></.link><!-- Good: Add screen reader text --><.link navigate={~p"/settings"}><.icon name="hero-cog-6-tooth" /><span class="sr-only">Settings</span></.link>
Vague link text
Error: "Link text is vague" (e.g., "click here", "read more")
<!-- Bad --><.link navigate={~p"/article/#{@article.id}"}>Read more</.link><!-- Good: Descriptive text --><.link navigate={~p"/article/#{@article.id}"}>Read more about <%= @article.title %></.link><!-- Good: Use aria-label for context --><.link navigate={~p"/article/#{@article.id}"} aria-label={"Read more about #{@article.title}"}>Read more</.link>
Headings
Skipped heading level
Error: "Heading levels should only increase by one"
<!-- Bad: h1 then h3 --><h1>Page Title</h1><h3>Section</h3><!-- Good: Sequential levels --><h1>Page Title</h1><h2>Section</h2><h3>Subsection</h3>
Multiple h1s
Error: "Page should have exactly one h1"
<!-- Ensure only one h1 per page --><h1><%= @page_title %></h1><!-- Use h2 for section headings --><section><h2>Features</h2></section>
Color Contrast
Low contrast text
Error: "Color contrast is too low"
<!-- Check your Tailwind colors --><!-- Bad: gray-400 on white (3.5:1 ratio) --><p class="text-gray-400">Low contrast text</p><!-- Good: gray-600 on white (5.7:1 ratio) --><p class="text-gray-600">Better contrast text</p><!-- Good: gray-700 on white (8.6:1 ratio) --><p class="text-gray-700">High contrast text</p>
Common Tailwind fixes:
text-gray-400→text-gray-600or darkertext-blue-400→text-blue-600or darker- For dark backgrounds, use
-100or-200variants
Modals/Dialogs
Modal accessibility
<divid="modal"role="dialog"aria-modal="true"aria-labelledby="modal-title"><h2 id="modal-title">Confirm Action</h2><p>Are you sure?</p><button phx-click="confirm">Yes</button><button phx-click="cancel">No</button></div>
Focus management: Use phx-mounted to trap focus:
# In your LiveViewdef handle_event("open_modal", _, socket) do{:noreply,socket|> assign(:show_modal, true)|> push_event("focus-modal", %{})}end
Dynamic Content
Live regions for updates
<!-- Announce changes to screen readers --><div aria-live="polite" aria-atomic="true"><%= if @flash[:info] do %><%= @flash[:info] %><% end %></div><!-- For important alerts --><div role="alert"><%= @error_message %></div>
Loading states
<div aria-busy={@loading}><%= if @loading do %><span class="sr-only">Loading...</span><.spinner /><% else %><%= @content %><% end %></div>
Phoenix-Specific Notes
Core Components
Phoenix 1.7+ includes accessible core components. Use them:
<.input /> <!-- Handles labels, errors, aria --><.button /> <!-- Proper button semantics --><.modal /> <!-- Focus management, aria --><.table /> <!-- Proper table structure -->
LiveView Forms
<.simple_form for={@form} phx-submit="save"><.input field={@form[:email]} type="email" label="Email" /><.input field={@form[:password]} type="password" label="Password" /><:actions><.button>Save</.button></:actions></.simple_form>
Flash Messages
Default Phoenix flashes are accessible. Don't break them:
<!-- Use the built-in flash component --><.flash_group flash={@flash} />
axe-core Configuration
axe-core violations include structured data with id, impact, description, helpUrl, and nodes. Each node contains the failing HTML element and fix suggestions.
For legitimate false positives, you can configure axe-core rule disabling in your test setup or via the --disable-rules flag:
mix excessibility --disable-rules=color-contrast,form-field-multiple-labels
Common ignore patterns for LiveView:
- Forms without visible submit buttons (phx-submit handles it)
- Dynamic content warnings during transitions
Verification
After fixing:
# Run axe-core on all snapshotsmix excessibility# Check specific testmix excessibility test/my_test.exs# Verify with fresh snapshotsrm -rf test/excessibility/html_snapshots/*mix test test/my_test.exsmix excessibility