Built from <div>s + CSS Grid

View code
Stop
Bus 1
Bus 2
Bus 3
Bus 4
Exeter Bus Station
07:15
07:45
08:15
08:45
High Street
07:22
07:52
08:22
08:52
Sidwell Street
07:28
07:58
08:28
08:58
Exeter College
07:35
08:05
08:35
09:05
Retail Park
07:44
08:14
08:44
09:14

This only reads as a table to a screen reader because of the role attributes added by hand. Take those away and it is just a grid of unrelated boxes. Striping the rows is trickier too: display: contents removes the row's own box, so the colour has to be applied to each row's cells individually rather than the row itself.

Built from a real <table>

View code
Bus 14 — Exeter Bus Station to Retail Park
Stop Bus 1 Bus 2 Bus 3 Bus 4
Exeter Bus Station 07:15 07:45 08:15 08:45
High Street 07:22 07:52 08:22 08:52
Sidwell Street 07:28 07:58 08:28 08:58
Exeter College 07:35 08:05 08:35 09:05
Retail Park 07:44 08:14 08:44 09:14

<thead>, <tbody> and scope give the row and column headings meaning automatically. No extra attributes needed.

Why this matters

Both boxes above show identical data and can be styled to look identical too. The difference shows up once you look underneath: the div version depends entirely on the role attributes to make any sense to a screen reader, and striping alternate rows means selecting the row and reaching down to its cells with a combinator, because display: contents leaves the row with no box of its own to colour. The table version gets both of those for free from the browser.