tags:
<%= content_tag :nav, aria: { label: 'Main Navigation' } do %>
<ul>
<li><%= link_to 'Home', root_path %></li>
<li><%= link_to 'About', about_path %></li>
<li><%= link_to 'Contact', contact_path %></li>
</ul>
<% end %>
This approach not only provides a clear structure for screen readers but also improves the overall maintainability of our code.
ARIA (Accessible Rich Internet Applications) attributes play a crucial role in enhancing the accessibility of dynamic content. Rails makes it easy to incorporate ARIA attributes into our views. For example, when implementing a collapsible section:
<%= button_tag 'Toggle Details',
aria: {
expanded: false,
controls: 'details-section'
},
data: {
action: 'click->toggle#toggle'
}
%>
<div id="details-section" aria-hidden="true">
<!-- Collapsible content here -->
</div>
In this example, we use ARIA attributes to indicate the expanded state of the button and its relationship to the content it controls. This information helps assistive technologies understand the structure and behavior of our interface.
Keyboard navigation is essential for users who can’t or prefer not to use a mouse. In Rails, we can enhance keyboard accessibility by ensuring all interactive elements are focusable and by implementing custom keyboard shortcuts where appropriate. Here’s an example of how we might implement a skip link to improve keyboard navigation:
<%= link_to 'Skip to main content', '#main-content', class: 'skip-link' %>
<!-- Rest of the header content -->
<main id="main-content" tabindex="-1">
<!-- Main content here -->
</main>
The skip link allows keyboard users to bypass repetitive navigation and jump straight to the main content. The tabindex=“-1” on the main element ensures it can receive focus programmatically.
Rails provides several built-in helpers that can improve accessibility. For instance, the image_tag helper automatically adds an empty alt attribute to images, reminding us to provide descriptive alternative text:
<%= image_tag 'logo.png', alt: 'Company Logo' %>
We can also use the label_tag helper to ensure form inputs are properly labeled:
<%= form_for @user do |f| %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.submit 'Sign Up' %>
<% end %>
These helpers encourage good accessibility practices right from the start of our development process.
When it comes to more complex accessibility requirements, there are several gems that can be incredibly helpful. The ‘axe-core-rails’ gem, for example, integrates the aXe accessibility testing engine into our Rails application, allowing us to run automated accessibility checks during development:
# In your Gemfile
gem 'axe-core-rails'
# In your application.js
//= require axe-core
# In your test suite
describe 'Home page' do
it 'should be accessible' do
visit root_path
expect(page).to be_axe_clean
end
end
This setup allows us to catch many accessibility issues early in the development process, saving time and ensuring a more inclusive final product.
Another useful gem is ‘rails_accessibility’, which provides a set of view helpers and components designed to improve accessibility. For instance, it offers an accessible_icon helper that wraps Font Awesome icons with proper ARIA attributes:
<%= accessible_icon('fas fa-search', 'Search') %>
This generates an icon with appropriate ARIA labels, ensuring that screen reader users understand its purpose.
Color contrast is a critical aspect of web accessibility. While Rails doesn’t provide built-in tools for managing color contrast, we can integrate external libraries like ‘color-contrast-checker’ to ensure our color choices meet WCAG standards:
require 'color-contrast-checker'
checker = ColorContrastChecker.new
background_color = '#FFFFFF'
text_color = '#333333'
if checker.check(background_color, text_color)
puts "Color combination passes WCAG AA standard"
else
puts "Color combination fails WCAG AA standard"
end
By incorporating such checks into our development workflow, we can ensure that our application’s color scheme remains accessible to users with visual impairments.
Form validation is another area where accessibility considerations are crucial. Rails provides built-in client-side validation, but we need to ensure that error messages are communicated effectively to all users. Here’s an example of how we might implement accessible form validation:
<%= form_for @user, data: { controller: 'form-validation' } do |f| %>
<div class="field">
<%= f.label :email %>
<%= f.email_field :email, required: true,
aria: {
describedby: 'email-error',
invalid: @user.errors[:email].any?
} %>
<% if @user.errors[:email].any? %>
<p id="email-error" class="error" role="alert">
<%= @user.errors[:email].first %>
</p>
<% end %>
</div>
<%= f.submit 'Submit' %>
<% end %>
In this example, we use ARIA attributes to associate error messages with their corresponding form fields and to indicate when a field is invalid. The role=“alert” attribute ensures that screen readers announce the error message when it appears.
Progressive enhancement is a key principle in creating accessible web applications. With Rails, we can implement progressive enhancement by starting with a solid, accessible HTML foundation and then layering on JavaScript functionality. For instance, when implementing a tabbed interface:
<div data-controller="tabs">
<ul role="tablist">
<li role="presentation">
<button id="tab1" role="tab" aria-selected="true" data-action="click->tabs#switchTab">
Tab 1
</button>
</li>
<li role="presentation">
<button id="tab2" role="tab" aria-selected="false" data-action="click->tabs#switchTab">
Tab 2
</button>
</li>
</ul>
<div id="panel1" role="tabpanel" aria-labelledby="tab1">
Content for Tab 1
</div>
<div id="panel2" role="tabpanel" aria-labelledby="tab2" hidden>
Content for Tab 2
</div>
</div>
This HTML structure is accessible even without JavaScript. When JavaScript is available, we can enhance the functionality with a Stimulus controller:
// app/javascript/controllers/tabs_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
switchTab(event) {
const selectedTab = event.currentTarget
const tabs = this.element.querySelectorAll('[role="tab"]')
const panels = this.element.querySelectorAll('[role="tabpanel"]')
tabs.forEach(tab => {
tab.setAttribute('aria-selected', tab === selectedTab)
})
panels.forEach(panel => {
panel.hidden = panel.getAttribute('aria-labelledby') !== selectedTab.id
})
}
}
This approach ensures that our application remains usable even if JavaScript fails to load or is disabled.
When dealing with dynamic content updates, it’s important to notify screen reader users of changes. The ‘rails-live-regions’ gem provides an easy way to implement ARIA live regions in our Rails applications:
# In your Gemfile
gem 'rails-live-regions'
# In your view
<%= live_region :messages %>
# In your controller
def create
# ... create message logic ...
live_region_update :messages, "New message from #{@message.sender}: #{@message.content}"
end
This setup ensures that screen reader users are notified of new messages as they arrive, without losing their place in the application.
Internationalization (i18n) is another important aspect of accessibility, as it allows us to cater to users who speak different languages or use different locales. Rails has excellent built-in support for i18n:
# config/locales/en.yml
en:
hello: "Hello world"
# config/locales/es.yml
es:
hello: "Hola mundo"
# In your view
<h1><%= t('hello') %></h1>
By using Rails’ i18n features, we can easily create applications that are accessible to a global audience.
As our applications grow more complex, it’s crucial to maintain focus management, especially after dynamic content updates. We can use Rails UJS or Stimulus to manage focus programmatically:
// app/javascript/controllers/modal_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
static targets = ["dialog", "closeButton"]
open() {
this.dialogTarget.showModal()
this.closeButtonTarget.focus()
}
close() {
this.dialogTarget.close()
this.element.querySelector("[data-action='modal#open']").focus()
}
}
This controller ensures that when a modal is opened, focus is moved to the close button, and when it’s closed, focus returns to the element that opened it.
Finally, it’s important to regularly test our applications for accessibility. While automated tools are helpful, they can’t catch everything. Manual testing with assistive technologies like screen readers is invaluable. I often use VoiceOver on macOS or NVDA on Windows to navigate through my Rails applications, listening for any inconsistencies or confusing interactions.
In conclusion, building accessible and WCAG-compliant web applications with Ruby on Rails requires a combination of semantic HTML, appropriate use of ARIA attributes, careful attention to keyboard navigation, and thoughtful implementation of Rails-specific tools and gems. By integrating these techniques into our development process, we can create web applications that are truly inclusive and accessible to all users.
Remember, accessibility is not a checkbox to be ticked off at the end of development. It’s an ongoing process that should be considered from the very beginning of our projects. As Rails developers, we have the power and responsibility to create web applications that everyone can use, regardless of their abilities. Let’s embrace this challenge and make the web a more inclusive place for all.