Accessibility

Simplified WCAG guidance

WCAG 2.2 – Levels A and AA only, with dev notes.

Budget calculator (form elements in table layout)

Example of how to accessibly associate inputs, without labels, to table headers. All inside an accessible, responsive table.

Example

Budget calculator
Monthly outgoing Amount you pay How often Monthly cost
Food, drink and clothing
e.g. groceries, eating out, drinks and clothes
0.00
Household goods and services
Including property maintenance and repairs, furnishing, flooring, household appliances etc.
0.00
Utility bills, council tax and buildings and contents insurance
e.g. gas, electricity, oil and water; council tax; phone and TV packages; TV licence and buildings and contents insurance
0.00
Transport
e.g. petrol, car tax and insurance, maintenance, public transport, season tickets and taxis
0.00
Total Monthly Outgoings 0.00

Upon a change in the table, the totals are politely read aloud by screen-readers, though it's not a requirement for it to be visible.

I also suggest using localStorage to retain entered values visit to visit on a single device. Possibly by a "Preserve values" checkbox.

The code

The table HTML layout
<!-- NOTE: Table must be usable at 320px screen width at 200% text-size. -->
<!-- Use either a figure or a div with role="region" to contain the table and provide overflow scolling -->  
<figure class="responsive_wrap" aria-labelledby="table-caption">
  
  <table class="responsive_table budget_calculator">
    <caption id="table-caption" role="caption">Budget calculator</caption>

    <thead>
      <tr>
        <th scope="col">Monthly outgoing</th>
        
        <!-- NOTE: Table header id's are referenced by aria-labelledby on both input and select -->
        <th scope="col" id="th_payment">Amount you pay</th>
        <th scope="col" id="th_frequency">How often</th>
        <th scope="col" style="white-space:nowrap">Monthly cost</th>
      </tr>
    </thead>

    <!-- NOTE: Roles are explicitly stated on table elements because at mobile sizes the elements have their default roles overwritten by the CSS -->
    <tbody role="rowgroup">
      
      <tr role="row">
        
        <!-- NOTE: role is required at mobile sizes, the data-header text becomes the visual row heading -->
        <th role="rowheader" class="td_description" scope="row" data-header="Monthly outgoing">
          <div>
            <!-- NOTE: Limit the amount of copy referenced by the input and select, not too verbose -->
            <div class="copy" id="copy1">Food, drink and clothing</div>
            <div class="help_copy">e.g. groceries, eating out, drinks and clothes</div>
          </div>
        </th>
        
        <td role="cell" class="td_payment" data-header="Amount you pay">

          <!-- NOTE: Input uses aria-labelledby to reference the column header and the rowheader copy --> 
          <!-- NOTE: The £ span wraps the input to prevent text wrapping at mobile screen widths -->
          <!-- NOTE: The input is limited to the keys: 0 to 9, commas, and periods. -->
          <span class="pound_icon"><input class="input_number" type="text" size="6" inputmode="decimal" autocomplete="off" aria-labelledby="th_payment copy1" onkeypress="return (event.key >= 0 && event.key <= 9) || event.key === '.' || event.key === ','">
        </td>
        <td role="cell" class="td_frequency" data-header="How often">
          <!-- NOTE: Select uses aria-labelledby to reference the column header and the rowheader copy --> 
          <!-- NOTE: The .select_wrap class is used to add a custom dropdown triangle to ensure cross-browser styling consistency --> 
          <span class="mobile_spacer_icon"><span class="select_wrap"><select class="select_frequency" aria-labelledby="th_frequency copy1">
            <option>Weekly</option>
            <option selected>Monthly</option>
            <option>Quarterly</option>
            <option>Yearly</option>
          </select></span></span>
        </td>
        <td role="cell" class="td_cost" data-header="Monthly cost"><span class="pound_icon monthly_outgoing">0.00</span></td>
      </tr>

      <!-- … More table rows … -->

    </tbody>
    
    <tfoot>
      <tr>
        <th colspan="3" scope="row">Total Monthly Outgoings</th>
        <td class="td_cost"><span class="pound_icon total_outgoings">0.00</span></td>
      </tr>
    </tfoot>
    
  </table>
</figure>

The most important part is associating the input/select with the column header and a limited part of the description or row header.

Original table

Original budget calculator with a popup showing small a detail

Layout comprising of unrelated divs, and unlabelled inputs, which only works for visual users. Descriptions are hiding behind a popup modal. Number inputs are left aligned, and the monthly cost is centred.

Weekly calculation incorrect too. Should be: value * 52 / 12

Design Notes