PATTERN NAME: Choice Matrix
COMPOSES: .table | .form-check / .form-check-input / .form-check-label | .form-label (on the legend) | .form-field

Several questions that share one answer scale — a school's yes/no enquiry questions. A matrix on a laptop, one labelled question per row on a phone, from one markup: the table is reflowed below sm, never duplicated. Narrow this window past 640px and watch a row you have already answered keep its answer.

Default

Default: the whole set is a <fieldset> with a <legend class="form-label"> — never a bare <div> with a heading. Each row is its own radio group: one name per row, never one name across the table, or answering the second question would unanswer the first.

Please answer each one
Question Yes No
Would you like a tour of the school
Would you like our prospectus posted
May we contact you about open days
<fieldset class="form-field">
  <legend class="form-label form-label-optional">Please answer each one</legend>
  <table class="table choice-matrix">
    <thead><tr><th><span class="tw:sr-only">Question</span></th><th class="choice-matrix-col" id="cm-yes">Yes</th>…</tr></thead>
    <tbody><tr>
      <td class="choice-matrix-question" id="cm-q1">Would you like a tour of the school</td>
      <td class="choice-matrix-col"><div class="form-check">
        <input class="form-check-input" type="radio" name="cm-tour" id="cm-tour-yes" aria-labelledby="cm-q1 cm-yes">
        <label class="form-check-label" for="cm-tour-yes">Yes</label>
      </div></td>…

The accessible name

Two ids, one name: every radio carries aria-labelledby="<question cell id> <column header id>", so it announces as "Would you like a tour of the school, Yes" — identical at both widths, because the name never comes from the in-cell label. That label exists so the answer is tappable on a phone; above sm the column header is what the eye reads and the label is out of the document, so a screen reader in table mode does not hear "Yes" twice.

A wider answer scale

Not only yes/no: any shared scale works — the columns are whatever the school asked. Column width is --choice-matrix-col (5.5rem); override it on the table when a scale needs wider headings.

How would you prefer we contact you about each of these
Question Email Post Never
Open day invitations
Admissions deadlines
<table class="table choice-matrix"> — one <th class="choice-matrix-col"> per answer, one <td class="choice-matrix-col"> per answer per row

Required, and not answered

State lives once, on the wrapper: .form-field.is-invalid tints every control in the set and reveals the .invalid-feedback. The message names what to do, not what went wrong with the machine.

Please answer each one
Question Yes No
Would you like a tour of the school
Would you like our prospectus posted

Choose Yes or No for each question

<fieldset class="form-field is-invalid"><p class="invalid-feedback" id="cmi-error">