Toggle Menu

Insights / Tech Tips / 508 Compliance and Agile

January 25, 2016

508 Compliance and Agile

11 mins read

Many agile teams treat 508 compliance as an afterthought. Teams typically build the product then retrofit it. If we want to build a modern website with accessibility in mind from day one, we need a better strategy. In general, we’d like to provide developers with tools that are 508 compliant from the beginning. But each form has its own set of unique accessibility constraints. By leveraging Rails’ unique templating library, we can build reusable components that have accessibility baked-in. In this post, we’ll show you how to use custom FormBuilders to generate reusable 508 compliant form elements.

Technical

To start with, we’ll create a very basic address form for our example. It consists of a handful of inputs and a set of radio buttons to denote the type of address. We’ll be showing both the Rails Form Builder ERB and the HTML that gets produced once that code is executed.

[pcsh lang=”ruby” tab_size=”4″ message=”” hl_lines=”” provider=”manual”]


# views/address/new.html.erb

<%= form_for Address.new, url: { action: "create" } do |f| %>
  <%= f.collection_radio_buttons :type, [['billing', 'Billing Address'] ,['mailing', 'Mailing Address']], :first, :last %>
  <%= f.text_field :street %>
  <%= f.text_field :city %>
  <%= f.text_field :state %>
  <%= f.text_field :zip %>
<% end %>

[/pcsh]

 

[pcsh lang=”ruby” tab_size=”4″ message=”” hl_lines=”” provider=”manual”]


<form class="new_address" id="new_address" action="/create" accept-charset="UTF-8" method="post" _lpchecked="1">
  <input name="utf8" type="hidden" value="✓">
  <input type="hidden" name="authenticity_token" value="VVMX2CXRHSrKSO0Tu9Wpxn/OyAtxHPy7kb5pmfqzjUpxSkNHtxcMuSilrXsbK7NoLjf7nkUGr75qbOTambPJ6w==">

  <input type="radio" value="billing" name="address[type]" id="address_type_billing"><label for="address_type_billing">Billing Address</label>
  <input type="radio" value="mailing" name="address[type]" id="address_type_mailing"><label for="address_type_mailing">Mailing Address</label>

  <input type="text" name="address[street]" id="address_street">
  <input type="text" name="address[city]" id="address_city">
  <input type="text" name="address[state]" id="address_state">
  <input type="text" name="address[zip]" id="address_zip">
</form>

[/pcsh]

This code creates a functional bare bones form. However, from a 508 perspective, there are a number of things wrong here.

  • All the inputs need labels
  • Associated radio buttons require fieldsets
  • Corresponding name/description for the related radio buttons should go in the legend

To address those shortcomings we could do something like the following:

[pcsh lang=”ruby” tab_size=”4″ message=”” hl_lines=”” provider=”manual”]


# views/address/new.html.erb

<%= form_for Address.new, url: { action: "create" } do |f| %>
  <%= content_tag(:fieldset) do %>
    <%= content_tag(:legend, 'Type') %>
    <%= f.collection_radio_buttons :type, [['billing', 'Billing Address'] ,['mailing', 'Mailing Address']], :first, :last %>
  <%= end %>

  <%= f.label :street %>
  <%= f.text_field :street %>

  <%= f.label :city %>
  <%= f.text_field :city %>

  <%= f.label :state %>
  <%= f.text_field :state %>

  <%= f.label :zip %>
  <%= f.text_field :zip %>
<% end %>

[/pcsh]

 

[pcsh lang=”ruby” tab_size=”4″ message=”” hl_lines=”” provider=”manual”]

<form class="new_address" id="new_address" action="/create" accept-charset="UTF-8" method="post">
  <input name="utf8" type="hidden" value="✓">
  <input type="hidden" name="authenticity_token" value="xShi7mTqSj7hrJzlOQnrY2a+V4UW25gautZ7KFrU2hvhMTZx9ixbrQNB3I2Z9/HNN0dkECLByx9BBPZrOdSeug==">

  <fieldset>
    <legend>Type</legend>
    <input type="radio" value="billing" name="address[type]" id="address_type_billing"><label for="address_type_billing">Billing Address</label>
    <input type="radio" value="mailing" name="address[type]" id="address_type_mailing"><label for="address_type_mailing">Mailing Address</label>
  </fieldset>

  <label for="address_street">Street</label>
  <input type="text" name="address[street]" id="address_street">

  <label for="address_city">City</label>
  <input type="text" name="address[city]" id="address_city">

  <label for="address_state">State</label>
  <input type="text" name="address[state]" id="address_state">

  <label for="address_zip">Zip</label>
  <input type="text" name="address[zip]" id="address_zip">
</form>

[/pcsh]


We’ve made progress, but these modifications have introduced redundancy. Ideally, we want to use the simple form syntax from before and have Rails handle accessibility.

Luckily, we can extend the Rails form builder to do just that. Now developers won’t need to keep track of all the different nuances; it’s all built in.

[pcsh lang=”ruby” tab_size=”4″ message=”” hl_lines=”” provider=”manual”]

# lib/forms/custom_builder.rb

class CustomFormBuilder < ActionView::Helpers::FormBuilder
end

[/pcsh]


Now we’re ready to include it in our form.

[pcsh lang=”ruby” tab_size=”4″ message=”” hl_lines=”” provider=”manual”]


# views/address/new.html.erb

<%= form_for Address.new, url: { action: "create" }, builder: CustomFormBuilder do |f| %>
...
<% end %>

[/pcsh]

This allows us to make customizations in two places: the view and the form builder itself.

The separation provides us with a few benefits:

  • The builder will take care of the HTML consistency and all the underlying 508 requirements
  • The view will be used to tell the builder what to build

Take these two lines from views/address/new.html.erb.

[pcsh lang=”ruby” tab_size=”4″ message=”” hl_lines=”” provider=”manual”]

<%= f.label :street %>
<%= f.text_field :street %>

[/pcsh]

508 requires all inputs to have labels describing them. Now, the FormBuilder can take care of this for us.

[pcsh lang=”ruby” tab_size=”4″ message=”” hl_lines=”” provider=”manual”]

# lib/forms/my_form_builder.rb

class CustomFormBuilder < ActionView::Helpers::FormBuilder
  def text_field(method, options={})
    label(:method)
    super(method, options)
  end
end

[/pcsh]

 

This allows us to make the input and label:

[pcsh lang=”ruby” tab_size=”4″ message=”” hl_lines=”” provider=”manual”]

<%= f.text_field :street %>

[/pcsh]

[pcsh lang=”ruby” tab_size=”4″ message=”” hl_lines=”” provider=”manual”]

<label for="address_street">Street</label>
<input type="text" name="address[street]" id="address_street">

[/pcsh]

We can also consolidate the code needed to give a collection_radio_button a fieldset by adding another method to our form builder:

[pcsh lang=”ruby” tab_size=”4″ message=”” hl_lines=”” provider=”manual”]

# lib/forms/custom_form_builder.rb

class CustomFormBuilder < ActionView::Helpers::FormBuilder
  # ...

  def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {})
    @template.content_tag(:fieldset) do
      input = @template.content_tag(:legend, method.to_s.titleize)
      input << super(method, collection, value_method, text_method, options, html_options)
      input
    end
  end
end

[/pcsh]

After both changes, our new form looks like this:

[pcsh lang=”ruby” tab_size=”4″ message=”” hl_lines=”” provider=”manual”]

# views/address/new.html.erb

<%= form_for Address.new, url: { action: "create" }, builder: CustomFormBuilder do |f| %>
  <%= f.collection_radio_buttons :type, [['billing', 'Billing Address'] ,['mailing', 'Mailing Address']], :first, :last %>
  <%= f.text_field :street %>
  <%= f.text_field :city %>
  <%= f.text_field :state %>
  <%= f.text_field :zip %>
<% end %>

[/pcsh]

Because we have a single place we’re making changes to the inputs we can easily add more customizations. Suppose we wanted to add inline input errors for all fields. With our original form, we would have to do something like this:

[pcsh lang=”ruby” tab_size=”4″ message=”” hl_lines=”” provider=”manual”]


# views/address/new.html.erb

<%= form_for Address.new, url: { action: "create" } do |f| %>
  <%= content_tag(:fieldset) do %>
    <%= content_tag(:legend, 'Type') %>
    <%= f.collection_radio_buttons :type, [['billing', 'Billing Address'] ,['mailing', 'Mailing Address']], :first, :last %>
    <%= content_tag(:span, f.errors[:method].first) if f.errors[:method].present? %>
  <%= end %>

  <%= f.label :street %>
  <%= f.text_field :street %>
  <%= content_tag(:span, f.errors[:method].first) if f.errors[:method].present? %>

  <%= f.label :city %>
  <%= f.text_field :city %>
  <%= content_tag(:span, f.errors[:method].first) if f.errors[:method].present? %>

  <%= f.label :state %>
  <%= f.text_field :state %>
  <%= content_tag(:span, f.errors[:method].first) if f.errors[:method].present? %>

  <%= f.label :zip %>
  <%= f.text_field :zip %>
  <%= content_tag(:span, f.errors[:method].first) if f.errors[:method].present? %>
<% end %>

[/pcsh]

However, 508 compliance requires errors to be associated with the input they’re showing for. One way to make this accessible is to surround the input and error with the label tag like so:
<%= f.label :street do %>
  <%= f.text_field :street %>
  <%= content_tag(:span, f.errors[:method].first) if f.errors[:method].present? %>
<% end %>
# With errors 

<label for="address_street">
  <span class="label-text">Street</span>
  <input type="text" name="address[street]" id="address_street">
  <span>Street is required</span>
</label>
This implementation isn’t very DRY. Let’s go back to our FormBuilder and DRY up the code.
# lib/forms/custom_form_builder.rb

class CustomFormBuilder < ActionView::Helpers::FormBuilder
  def text_field(method, options={})
    label(method) do
      input = @template.content_tag(:span, method.to_s.titleize, class: 'label-text')
      input << super(method, options)
      input << @template.content_tag(:span, object.errors[:method].first, class: 'error-text') if object.errors[:method].present?
      input
    end
  end

  def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {})
    @template.content_tag(:fieldset) do
      input = @template.content_tag(:legend, method.to_s.titleize, class: 'legend-text')
      input << super(method, collection, value_method, text_method, options, html_options)
      input << @template.content_tag(:span, object.errors[:method].first, class: 'error-text') if object.errors[:method].present?
      input
    end
  end
end

By making a couple of changes in one place, we can go back to the clean form and it will produce the same markup as above.

[pcsh lang=”ruby” tab_size=”4″ message=”” hl_lines=”” provider=”manual”]

# views/address/new.html.erb

<%= form_for Address.new, url: { action: "create" }, builder: CustomFormBuilder do |f| %>
  <%= f.collection_radio_buttons :type, [['billing', 'Billing Address'] ,['mailing', 'Mailing Address']], :first, :last %>
  <%= f.text_field :street %>
  <%= f.text_field :city %>
  <%= f.text_field :state %>
  <%= f.text_field :zip %>
<% end %>

[/pcsh]

 

[pcsh lang=”ruby” tab_size=”4″ message=”” hl_lines=”” provider=”manual”]

<form class="new_address" id="new_address" action="/create" accept-charset="UTF-8" method="post" _lpchecked="1">
  <input name="utf8" type="hidden" value="✓">
  <input type="hidden" name="authenticity_token" value="tRSBbSviPWZf8aoDGDSgvgmdOtQgJ2XgvyqMzPTOyEkvsbUgseGMXrV2km1T8e/9voK3r0r2CcInpbWFhPbKoA==">

  <fieldset>
    <legend class="legend-text">Type</legend>
    <input type="radio" value="billing" name="address[type]" id="address_type_billing"><label for="address_type_billing">Billing Address</label>
    <input type="radio" value="mailing" name="address[type]" id="address_type_mailing"><label for="address_type_mailing">Mailing Address</label>
  </fieldset>

  # This input has an error
  <label for="address_street">
    <span class="label-text">Street</span>
    <input type="text" name="address[street]" id="address_street">
    <span>Street is required</span>
  </label>

  <label for="address_city">
    <span class="label-text">City</span>
    <input type="text" name="address[city]" id="address_city">
  </label>

  <label for="address_state">
    <span class="label-text">State</span>
    <input type="text" name="address[state]" id="address_state">
  </label>

  <label for="address_zip">
    <span class="label-text">Zip</span>
    <input type="text" name="address[zip]" id="address_zip">
  </label>
</form>

[/pcsh]

 

Conclusion

The purpose of this article was to start Agile teams thinking about 508 compliance from the beginning and to outline some helpful practices for making compliance easy and natural. Once again, by acknowledging 508 early, teams should be able to avoid any long overdue facelifts in order to make a site 508 compliant. Furthermore, outside of 508 compliance, well-maintained code libraries and central components can decrease the potential for developer errors, maintain a consistent look and feel, and DRY up the code base.

Additional Resources

You Might Also Like

Resources

Simplifying Tech Complexities and Cultivating Tech Talent with Dustin Gaspard

Technical Program Manager, Dustin Gaspard, join host Javier Guerra, of The TechHuman Experience to discuss the transformative...

Resources

How Federal Agencies Can Deliver Better Digital Experiences Using UX and Human-Centered Design

Excella UX/UI Xpert, Thelma Van, join host John Gilroy of Federal Tech Podcast to discuss...