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
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
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
- Associate the input/select with the column header and a limited part of the description or row header.
- Align numbers with decimal places on the period.
- Align numbers without decimal places to the right.
- Consider using a monospace font for real numbers to keep alignments from drifting.
- Use a comma as a separator for thousands and millions.
- Size input widths to the expected width of the content.
- The select dropdown arrow was created in CSS for cross-browser styling compatability.