Simple_form: Add support for aria-labelledby and aria-describedby

Created on 10 Dec 2014  路  8Comments  路  Source: heartcombo/simple_form

In using SimpleForm we are often finding that we need aria-labelledby and aria-describedby; I have a rudimentary solution in place but it looks very fragile and subject to breakage as SimpleForm moves forward.

I'm exploring adding a new component (akin to the SimpleForm::Components::HTML5) for SimpleForm::Components::AriaLabelledBy and SimpleForm::Components::AriaDescribedBy.

I'm presently looking at how best to go about submitting a pull request for this behavior. Its looking like a rather lengthy process as I orient to the code of SimpleForm. I want to make sure that this is something that would be a reasonable addition to the SimpleForm ecosystem.

Most helpful comment

For others finding this issue later, I got this working by using:

input_html: { aria: { labelledby: 'your-label' } }

maybe this is obvious for some, but it took a bit of time to get it right.

All 8 comments

I believe it is not a good fit for the library but I agree we should make easier to create and register new components.

See #1176

Thank you for the prompt response. And more importantly, thank you for your time on SimpleForm (and other open soure projects).

I spent a bit of time exploring adding a component, and due to the inter-relation of labels and inputs the aria-labelledby bled into the other components. It was a bit confusing for me.

For reference, I was able to "cheat" and bring aria-labelledby and aria-describedby behaviors into play via the following. It is not complete, but works for our narrow case. It does provide some potential guidance for others; Though a proper component would be a better solution.

SimpleForm.setup do |config|
  config.wrappers :bootstrap, :tag => 'div', :class => 'control-group', :error_class => 'error' do |b|
    b.use :html5
    b.use :placeholder
    b.wrapper :tag => 'span', :class => 'control-label' do |bb|
      bb.use :label
      bb.use :override_hint
    end
    b.wrapper :tag => 'div', :class => 'controls' do |ba|
      ba.use :override_help
      ba.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
    end
  end
end

module SimpleForm
  module Components
    module Labels
      # I chose Labels because it extends ActiveSupport::Concern and thus
      # I can more readily guarantee that the below module is mixed into all
      # classes, SimpleForm::Inputs::Base and its descendant classes
      included do
        include SimpleFormOverride::Labels
      end
    end
  end
end

module SimpleFormOverride
  module Labels
    extend ActiveSupport::Concern
    # To provide the proper arialabelled-by attribute, I'm adding an ID to the
    # label that can be referenced.
    def raw_label_text
      template.content_tag('span', id: override_dom_id('label')) do
        super
      end
    end

    def hint_override
      return '' unless has_hint?
      template.content_tag('span', class: 'field-hint', role: 'tooltip', id: override_dom_id('hint')) do
        hint
      end
    end

    def input_override
      @input_html_options[:'aria-labelledby'] = override_dom_id('label')
      @input_html_options[:'aria-describedby'] = override_dom_id('hint')
      input
    end

    private

    def override_dom_id(suffix)
      "#{object_name}_#{attribute_name}_#{suffix}"
    end
  end
end

Is there any progress in this? I'm facing the task to make Rails forms more accessible which needs to assign the validation errors to the specific input fields so screenreader users see them. Also, the focus should be set into the first invalid input field.

At the time being, I'm simply using a JavaScript workaround to do this:

class App.FormErrorsAccessibilizer
  constructor: (el) ->
    @$el = $(el) # Always pass an element to the constructor and make it available as a jQuery selector!
    @formGroups = @$el.find('.form-group.has-error')

    if @formGroups.size() > 0
      @describe_inputs_with_help_blocks()
      @focus_first_input_with_help_block()

  describe_inputs_with_help_blocks: ->
    for formGroup in @formGroups
      $formGroup = $(formGroup)

      $help  = $formGroup.find('.help-block')
      $input = $formGroup.find(':input')

      input_id = $input.attr('id')
      help_id  = "#{input_id}_help"

      $help.attr('id', help_id)
      $input.attr('aria-describedby', help_id)

  focus_first_input_with_help_block: ->
    @formGroups.first().find(':input').focus()

This works nicely, but it's a bit error-prone and hard to test. It would be very nice if Simple_Form would offer some accessibility plugin.

If anyone is interested in my specs, here they are:

require 'rails_helper'

describe 'Accessibility' do
  describe 'form validations' do
    it 'assigns the help blocks with the inputs through aria-describedby', js: true do
      visit new_user_registration_path
      click_button 'Sign up'

      expect(page).to have_css 'input#user_name[aria-describedby="user_name_help"]'
      expect(page).to have_css '#user_name_help'
    end

    it 'sets the focus to the first invalid element', js: true do
      visit new_user_registration_path
      fill_in 'user_name', with: 'daisy' # Make sure the first occurring element is valid, so explicitly the second one (email) should gain focus
      click_button 'Sign up'

      # Check for focused element, see http://stackoverflow.com/questions/7940525/testing-focus-with-capybara
      expect(page.evaluate_script('document.activeElement.id')).to eq 'user_email'
    end
  end
end

I just had another idea, something like fixing this problem in the configuration:

      ba.use :input, aria: {describedby: 'xyz_error xyz_hint'} # xyz would be the input's ID
      ba.use :error, wrap_with: { tag: 'span', class: 'help-block', id: 'xyz_error' }
      ba.use :hint,  wrap_with: { tag: 'p', class: 'help-block', id: 'xyz_hint' }

The tricky part here is that aria-describedby should only be assigned to the input when there's an alert and/or a hint. Any idea on how to accomplish this? One could maybe monkey-patch the Input class, but I don't know whether this is the best approach.

@jmuheim I believe the changes I proposed might work for what you need. But I've had to set them aside. Keep me posted if you dive into this.

Have there been any developments on this topic? We're in the same boat, where something like https://github.com/plataformatec/simple_form/issues/1175#issuecomment-86887671 would be awesome. Thanks!

@jeremyf I realize that your work on this was a long time ago, but I wonder if you've had any chance to look at it further. It would be very valuable to me to see a more complete implementation.

For others finding this issue later, I got this working by using:

input_html: { aria: { labelledby: 'your-label' } }

maybe this is obvious for some, but it took a bit of time to get it right.

Was this page helpful?
0 / 5 - 0 ratings