Question, is there a reason that you are using tables? Is this part of something bigger, where tables make sense?
If it is just for layout, then it might be better to use a form and a more semantic approach.
I have been having a play. This is your table using a form instead. I have made use of CSS grid to give it a table like structure.
<div class='container'>
<form action='' id='tax-calculator'>
<h2>Tax Calculator</h2>
<fieldset id='applications' class='form-fieldset'>
<div class='title-headings'>
<h4>Type</h4>
<h4>Price</h4>
</div>
<div class='checkbox'>
<input type='checkbox' id='applications-4' name='applications-4' value='556.23'>
<label for='applications-4' >4 Applications</label>
<span class='checkbox-value'>556.23</span>
</div>
<div class='checkbox'>
<input type='checkbox' id='applications-3' name='applications-3' value='556.23'>
<label for='applications-3'>3 Applications</label>
<span class='checkbox-value'>556.23</span>
</div>
<div class='checkbox'>
<input type='checkbox' id='aeration' name='aeration' value='556.23'>
<label for='aeration'>Aeration</label>
<span class='checkbox-value'>556.23</span>
</div>
<div class='checkbox'>
<input type='checkbox' id='aeration-overseed' name='aeration-overseed' value='548.23'>
<label for='aeration-overseed'>Aeration Overseed</label>
<span class='checkbox-value'>548.23</span>
</div>
<div class='checkbox'>
<input type='checkbox' id='c20-application' name='c20-application' value='556.23'>
<label for='c20-application'>C20 Application</label>
<span class='checkbox-value'>556.23</span>
</div>
</fieldset>
</form>
<h3>Total:</h3>
<output id='total'>$0.00</output>
</div>
I haven’t tried your table layout for accessibility, but my approach does allow for navigation using keys, tab to tab down, shift-tab to tab up and spacebar to toggle the checkboxes.
The other nice thing in my opinion is that in the DOM the fieldset has an elements property, which is a HTMLCollection of all it’s inputs. This makes it easy to select those elements in Javascript.
Here is my Javascript revision
// helper methods
// shorthand for Array.prototype.flatMap
const flatMap = [].flatMap;
// sums the total of an array of numbers
const sum = (nums) => nums.reduce((x, y) => x + y, 0);
const formatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
const form = document.querySelector('#tax-calculator');
const output = document.querySelector('output#total');
form.addEventListener('change', function (event) {
// find the closest fieldset parent of the changed element
const fieldset = event.target.closest('fieldset');
// fieldsets have an elements property that contains an HTMLCollection of it's input elements
// try console.dir(fieldset.elements) to see what it looks like
const values = flatMap.call(
fieldset.elements,
(elem) => elem.checked ? [parseFloat(elem.value)] : []
);
output.textContent = formatter.format(sum(values));
});
A couple of things. I have created a sum helper function called sum to total the checked values.
// sums the total of an array of numbers
const sum = (nums) => nums.reduce((x, y) => x + y, 0);
This function takes an array of numbers, adds them up and returns the total.
e.g.
sum([1,2,3]) → 6
You can read about the reduce function here
Again I have used flatMap to filter and map, in this case to filter the checked values.
// find the closest fieldset parent of the changed element
const fieldset = event.target.closest('fieldset');
// fieldsets have an elements property that contains an HTMLCollection of it's input elements
const values = [].flatMap.call(
fieldset.elements,
// empty arrays are discarded in the flattening process
// using this we can filter results
(elem) => elem.checked ? [parseFloat(elem.value)] : []
);
Just another approach @info3382
Codepen here