Accessibility

Simplified WCAG guidance

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

A table-like layout using buttons as cards

The question arose; Should I use clickable table rows or individual cards? The conclusion I came to was to use cards.

Example

Click a card button to see what would actually be read aloud by a screen-reader.

The code

HTML
HTML
<div class="cards_wrap">

  <!-- Visual only, not read aloud by screen-readers -->
  <!-- Do NOT hide if interactive - EG Sorting by column -->
  <div class="card_column_headers" aria-hidden="true">
    <div class="name">Payee name</div>
    <div class="mandate">Mandate</div>
    <div class="date">Date last payed</div>
    <div class="amount">Amount</div>
  </div>

  
  <ul class="card_list" role="list">

    <li>
      <button class="card" type="button" aria-labelledby="labelledby2" aria-describedby="describedby2">

        <!-- Totally hidden, only referenced by screen-readers. -->
        <!-- Audio order may be better in a different order to the visual order -->
        <!-- NOTE: Use punctuation! -->
        <div hidden id="labelledby2">
          Name: Mick Jagger,
          Amount: £7,890.12,
          Date: 10th January 2025
        </div>
        <!-- NOTE: Use punctuation! -->
        <div hidden id="describedby2">
          Mandate: 021,
          Role: Vocalist,
          Sort code: 12-34-56,
          Account number: 1 2 3 4 5 6 7 8
        </div>

        <!-- Visual only, not read aloud by screen-readers -->
        <div aria-hidden="true">
          <div>
            <div class="name">Mick Jagger</div>
            <div class="account">12-34-56 | 12345678</div>
            <div class="role">Vocalist</div>
          </div>
          <div class="mandate">021</div>
          <div class="date">10/01/25</div>
          <div class="amount">£7,890.12</div>
        </div>
      </button>
    </li>

    <!-- ... More li elements with buttons ... -->

  </ul>
</div>
CSS
CSS
/* The button cards */

.cards_wrap {
  /* Ensure 4.5:1 colour contrast ratio between foreground and background colours. Border colour needs to be 3:1 */
  --_header-color: #1d7a9f;
  --_header-bg-color: #f5f9fb;
  --_card-color: #444;
  --_card-secondary-color: #727272;
  --_card-bg-color: #fdfdfd;
  --_card-bg-color-even: #f5f9fb;
  --_card-border-color: #CEDEE7;
  --_card-focus-outline-color: blue;
  line-height: 1.3;
}

/* Visual-only layout within the button card */
.cards_wrap div[aria-hidden=true] {
  display: grid;
  grid-gap: 1rem;

  /* A simple grid here, a real world pattern is beyond scope */
  grid-template-columns: 1fr 1fr 1fr 1fr;
}

.card_column_headers {
  color: var(--_header-color, #333);
  background-color: var(--_header-bg-color, #f7f7f7);
  font-size: smaller;
  font-weight: 700;
}
.card_column_headers,
.card {
  padding: 8px 12px;
}

/* role="list" enforces list semantics for accessibility */
.card_list[role="list"] {
  list-style: none;
  padding:0;
}
.card_list li {
  margin-top: 1rem;
}
.card {
  outline: initial;
  font: inherit;
  color: var(--_card-color, #333);
  background-color: var(--_card-bg-color, #f7f7f7);
  border: 1px solid var(--_card-border-color, #959595);
  width: 100%;
  text-align: left;
  cursor: pointer;
}
li:nth-child(even) > .card {
  background-color: var(--_card-bg-color-even, red);
}
.card * {
  pointer-events: none;
}

/* Unique settings for specific fields */

.cards_wrap .amount {
  /* Always right align numbers with decimal places */
  text-align: right;
}

.card .name {
  font-weight: 500;
}

.card :is(.account, .role) {
  font-size: .8rem;
  font-weight: normal;
  color: var(--_card-secondary-color, #333);
}

/* Keyboard-only focus rings */

a, button, summary, input, select, textarea, [contenteditable], [tabindex], .card {
  outline: 2px solid transparent;
  outline-offset: 8px;
}
:is(a, button, summary, input, select, textarea, [contenteditable], [tabindex], .card):focus-visible {
  outline-color: var(--_input-focus-outline-color, blue);
  outline-offset: 2px;
  transition: all .3s ease-out;
  transition-property: outline-color, outline-offset;
}

Features

Caveat: Reflow at mobile viewports, 200% font-size, and dark mode, are beyond the scope of this demo.