一键导入
e11y-fix
Reference guide for fixing axe-core/WCAG errors in Phoenix LiveView - maps common violations to Phoenix-specific fixes
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Reference guide for fixing axe-core/WCAG errors in Phoenix LiveView - maps common violations to Phoenix-specific fixes
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when debugging LiveView issues - timeline analysis shows state at each event, snapshots show rendered HTML, analyzers suggest root causes
Use when implementing Phoenix LiveView features - TDD with html_snapshot for state inspection and axe-core for accessibility. Sprinkle snapshots to see what's rendered, delete when done.
| name | e11y-fix |
| description | Reference guide for fixing axe-core/WCAG errors in Phoenix LiveView - maps common violations to Phoenix-specific fixes |
Quick reference for fixing axe-core/WCAG errors in Phoenix LiveView applications.
mix excessibility to see axe-core errorshtml_snapshot(view) to capture the problematic statemix excessibility to verify| 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 |
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" />
Note: LiveView forms using phx-submit often don't need a visible submit button. axe-core may flag this. You can:
aria-label on 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>
<.input
field={@form[:email]}
type="email"
label="Email"
required
/>
<!-- Renders with aria-required="true" -->
<.input
field={@form[:email]}
type="email"
label="Email"
errors={@form[:email].errors}
/>
<!-- Errors should be associated via aria-describedby -->
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>
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>
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>
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>
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>
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-600 or darkertext-blue-400 → text-blue-600 or darker-100 or -200 variants<div
id="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 LiveView
def handle_event("open_modal", _, socket) do
{:noreply,
socket
|> assign(:show_modal, true)
|> push_event("focus-modal", %{})}
end
<!-- 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>
<div aria-busy={@loading}>
<%= if @loading do %>
<span class="sr-only">Loading...</span>
<.spinner />
<% else %>
<%= @content %>
<% end %>
</div>
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 -->
<.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>
Default Phoenix flashes are accessible. Don't break them:
<!-- Use the built-in flash component -->
<.flash_group flash={@flash} />
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:
After fixing:
# Run axe-core on all snapshots
mix excessibility
# Check specific test
mix excessibility test/my_test.exs
# Verify with fresh snapshots
rm -rf test/excessibility/html_snapshots/*
mix test test/my_test.exs
mix excessibility