statamic / ideas

💡Discussions on ideas and feature requests for Statamic
https://statamic.dev
30 stars 1 forks source link

Improve accessibility in prerendered form fields #1069

Open royvanv opened 8 months ago

royvanv commented 8 months ago

Currently, the prerendered form fields have no id attribute, so they can not be linked in the <label> using the for attribute. This hurts accessibility.

It would nice to have an autogenerated ID property in the field object. Probably the handle with a random suffix, to prevent duplicate IDs in the HTML of the page. In the templates these IDs should be used in the id attribute.

Antlers template:

{{ fields }}
    <div class="mb-2">
        <label for="{{ someGeneratedId }}" class="block">{{ display }}</label>
        {{ field }}
    </div>
{{ /fields }}

Output:

<div class="mb-2">
    <label for="name-a382c0" class="block">Name</label>
    <input type="text" name="name" id="name-a382c0" value="">
</div>
benfurfie commented 8 months ago

It might be better to wrap the input and error in the label to make rendering simpler. E.g.

<div class="mb-2">
    <label class="block">
        <span class="block">Name</span>
        <input type="text" name="name" value="">
        <span>Error</span>
    </label>
</div>

But I agree this would be a good improvement.

rachelmflowers commented 2 months ago

I was just about to create a similar issue. I actually have a few additional suggestions for accessibility.

I also personally prefer NOT to have my labels surround the inputs (unless they're checkboxes). But that's mainly personal preference

The default form views are missing some basic attributes necessary for making forms more accessible and easier to navigate with a screen reader.

Here's an overview of suggested updates.

  1. The form must indicate to a user how required fields are marked
  2. All labels should have a "for" attribute the corresponds to the input fields "id" attribute value
  3. All required fields need to have a visual indicator to show they are required and differentiate between non-required fields
  4. Form errors should include aria attributes to announce to a user when there is an error and where to find a description of the error
  5. Checkbox and radio groups must be surrounded by a <fieldset> with a <legend>

Here is how we have handled these requirements: Forms > fields.antlers.html

{{ fields }}
    {{ if type != checkboxes || radio }}
        <div class="p-4 {{ error ?= "has-error" }}">
            <label for="{{ handle }}">
                {{ display }} {{ (validate | contains:required) ?= "<sup class='text-red-800'>*</sup>" }}
            </label>
            <div class="p-2">{{ field }}</div>
    {{ else }}
        <fieldset class="p-4" {{ if error }}aria-invalid="true" aria-describedby="{{ handle }}-error"{{ /if }}>
            <legend>{{ instructions }}</legend>  
            <div class="p-2">{{ field }}</div>
    {{ /if }}

    {{ if error }}
        <p id="{{ handle }}-error" class="text-red-800" aria-live="assertive">{{ error }}</p>
    {{ /if }}

    {{ type != 'checkboxes' || 'radio' ? '</div>' : '</fieldset>' }}
{{ /fields }}

Then for ALL fields the following attributes need to be added:

  1. id
  2. aria-invalid and aria-describedby if there is an error

As shown here in the forms > fields > default.antlers.html file:

<input
    id="{{ handle }}"
    type="{{ input_type ?? 'text' }}"
    name="{{ handle }}"
    value="{{ value }}"
    {{ if placeholder }}placeholder="{{ placeholder }}"{{ /if }}
    {{ if character_limit }}maxlength="{{ character_limit }}"{{ /if }}
    {{ if js_driver }}{{ js_attributes }}{{ /if }}
    {{ if validate|contains:required }}required{{ /if }}
    {{ if error }}aria-invalid="true" aria-describedby="{{ handle }}-error"{{ /if }}
>

Happy to get additional feedback from anyone else that has to meet WCAG AA standards :)