Not a finished answer as I will make fiddles for each scenario, just wanted to get the thought process down for you to play with - UPDATE: 3 / 4 fiddles done
I have played with a lot of options here, fun challenge!
Short Answer
The short answer to your question is no, you cannot keep the structure given, make it responsive and keep it accessible using only CSS.
I would be so bold as to say it is impossible to do what you ask, as it would involve using some sort of display properties that would break the accessibility tree (i.e. if we used display: block
that immediately breaks the accessibility tree).
I even considered using position: absolute
(a terrible idea but wanted to see if it could be done) but that also immediately breaks the accessibility tree.
Due to the structure of the data the only way I could think to do this with CSS only would involve hundreds of :before
, :after
and content:
CSS properties / selectors that would just be a mess. Therefore i didn't explore that option but it could be possible that way for someone determined enough to make this work.
Why would this not be possible?
I think the answer heralds back to the good ol' days when web designers used tables for layout. The second any positioning is applied the browser assumes that you are using a table for layout and so removes it from the accessibility tree (as it would make a website horrendous to use if left in the accessibility tree and a table was used for layout!)
What are the options then?
I have a few different ways you could achieve the end result. I will attempt to weigh the pros and cons for each, but every single one of them breaks at least one of your requirements.
Option 1. Create a second table for mobile view and switch them out using media queries.
By creating a second table you can present the information differently for mobile vs tablet and desktop.
I am quite surprised at how many positives I found for this method.
- It allows you to maintain your original markup (albeit you have to add markup for the second table)
- Switching out the tables is literally one CSS selector (
display: none
or display: table
) within a media query.
- Accessibility would be maintained (assuming you build the second table correctly)
- Performance - I actually thought adding a second table would be bad for load times but after some though I realised that it will likely make very little difference as the reduced CSS requirement would counter the increased HTML, plus tables compress well with GZIP etc.
- Doesn't require any JavaScript (but it would require creating the table twice either programatically or manually in the backend)
Option 1 example
(view full screen for desktop / original view)
Thinking I had as I built this example - I am actually unsure whether the following table is semantically correct. It is a bit unusual adding <th>
in any place other than headers and footers / first columns in a table. It passes validation and seems ok on a screen reader but still you may want to check it yourself.
table {
border-collapse: collapse;
border-spacing: 0
}
table th {
text-align: left;
background-color: #ccc
}
table th,
table td {
padding: .5em;
border: 1px solid #999
}
/*start with mobile view hidden*/
table:nth-child(2){
display:none;
}
@media only screen and (max-width: 720px) {
/*toggle second table in for mobile view*/
table:nth-child(1){
display:none;
}
table:nth-child(2){
display:table;
}
}
<table>
<tr>
<td></td>
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
</tr>
<tr>
<th>09:00 - 11:00</th>
<td>Closed</td>
<td>Open</td>
<td>Open</td>
<td>Closed</td>
<td>Closed</td>
</tr>
<tr>
<th>11:00 - 13:00</th>
<td>Open</td>
<td>Open</td>
<td>Closed</td>
<td>Closed</td>
<td>Closed</td>
</tr>
<tr>
<th>13:00 - 15:00</th>
<td>Open</td>
<td>Open</td>
<td>Open</td>
<td>Closed</td>
<td>Closed</td>
</tr>
<tr>
<th>15:00 - 17:00</th>
<td>Closed</td>
<td>Closed</td>
<td>Closed</td>
<td>Open</td>
<td>Open</td>
</tr>
</table>
<table>
<tr>
<th colspan="2">Monday</th>
</tr>
<tr>
<td>09:00 - 11:00</td>
<td>Closed</td>
</tr>
<tr>
<td>11:00 - 13:00</td>
<td>Open</td>
</tr>
<tr>
<td>13:00 - 15:00</td>
<td>Open</td>
</tr>
<tr>
<td>15:00 - 17:00</td>
<td>Closed</td>
</tr>
<tr>
<th colspan="2">Tuesday</th>
</tr>
<tr>
<td>09:00 - 11:00</td>
<td>Open</td>
</tr>
<tr>
<td>11:00 - 13:00</td>
<td>Open</td>
</tr>
<tr>
<td>13:00 - 15:00</td>
<td>Open</td>
</tr>
<tr>
<td>15:00 - 17:00</td>
<td>Closed</td>
</tr>
<tr>
<th colspan="2">Wednesday</th>
</tr>
<tr>
<td>09:00 - 11:00</td>
<td>Open</td>
</tr>
<tr>
<td>11:00 - 13:00</td>
<td>Closed</td>
</tr>
<tr>
<td>13:00 - 15:00</td>
<td>Open</td>
</tr>
<tr>
<td>15:00 - 17:00</td>
<td>Closed</td>
</tr>
<tr>
<th colspan="2">Thursday</th>
</tr>
<tr>
<td>09:00 - 11:00</td>
<td>Closed</td>
</tr>
<tr>
<td>11:00 - 13:00</td>
<td>Closed</td>
</tr>
<tr>
<td>13:00 - 15:00</td>
<td>Closed</td>
</tr>
<tr>
<td>15:00 - 17:00</td>
<td>Open</td>
</tr>
<tr>
<th colspan="2">Friday</th>
</tr>
<tr>
<td>09:00 - 11:00</td>
<td>Closed</td>
</tr>
<tr>
<td>11:00 - 13:00</td>
<td>Closed</td>
</tr>
<tr>
<td>13:00 - 15:00</td>
<td>Closed</td>
</tr>
<tr>
<td>15:00 - 17:00</td>
<td>Open</td>
</tr>
</table>
Option 2. Just make the table scrollable on mobile.
Another one where on paper it looks like a good option but I just don't like it. Having a table that scrolls is one of the worst mobile experiences I can think of (exception being massive data sets).
It is also a questionable accessibility point, screen readers would be fine but is a scrollable table accessible for those with accuracy issues? Probably not.
Option 2 Example
Just make the screen smaller than 720px to see the scroll bars appear for horizontal scrolling.
table {
border-collapse: collapse;
border-spacing: 0;
min-width: 720px;
}
table th {
text-align: left;
background-color: #ccc
}
table th,
table td {
padding: .5em;
border: 1px solid #999
}
DIV{
overflow-x:auto;
}
<div>
<table>
<tr>
<td></td>
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
</tr>
<tr>
<th>09:00 - 11:00</th>
<td>Closed</td>
<td>Open</td>
<td>Open</td>
<td>Closed</td>
<td>Closed</td>
</tr>
<tr>
<th>11:00 - 13:00</th>
<td>Open</td>
<td>Open</td>
<td>Closed</td>
<td>Closed</td>
<td>Closed</td>
</tr>
<tr>
<th>13:00 - 15:00</th>
<td>Open</td>
<td>Open</td>
<td>Open</td>
<td>Closed</td>
<td>Closed</td>
</tr>
<tr>
<th>15:00 - 17:00</th>
<td>Closed</td>
<td>Closed</td>
<td>Closed</td>
<td>Open</td>
<td>Open</td>
</tr>
</table>
</div>
Option 3. Don't use a table at all and instead use different markup and make it look like a table.
This may seem like a stupid idea, and in a lot of ways it is! The CSS would be hard work and it isn't semantically correct.
Although the markup would end up being semantically correct (from a document structure perspective) as you have a "heading" (<h2>Monday</h2>
) and then a "sub-heading" (<h3>09:00 - 11:00</h3>
) followed by relevant information <p>Closed</p>
. It just means the visual styling is not quite the same as the HTML information.
The only advantage to this method is we don't have to rely on WAI-ARIA
in order to make it work and we can position things however we want visually without impacting screen readers. WAI-ARIA isn't 100% perfect support, so this could be the most accessible solution of them all. Down side is it is a complete change of data from what you started with.
Option 3 Example
Please do a better CSS job of this than me, but the principle is sound.
Notice how I have the aria-hidden
section at the beginning for the 'desktop' view (to hide it from screen readers but not visually) and how I use the visually hidden technique on the .col h3
selector. This way as far as a screen reader is concerned the HTML is exactly the same for both.
.container{
width: 60%;
}
@media only screen and (min-width: 1024px) {
h2, .desktop h3, p{
outline: 1px solid #333;
outline-offset: -1px;
padding: 0px 10px;
margin: 0;
}
div{
float: left;
width: 15%;
line-height: 2rem;
}
h2{
line-height: 3rem;
}
.col h3{
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
clip: rect(1px, 1px, 1px, 1px);
white-space: nowrap; /* added line */
}
.desktop{
width: 25%;
margin-top: 3rem;
float:left;
display: block;
}
.desktop h3{
display: block;
line-height: 2rem;
}
}
@media only screen and (max-width: 1023px) {
.desktop{
display: none;
}
h2{
width: 100%;
display: block;
float: left;
padding: 10px 0px 10px 0px;
margin: 5px 0;
border-top: 1px solid #666;
border-bottom: 1px solid #666;
}
h3{
width: 50%;
display: block;
float:left;
line-height: 2rem;
padding: 0px;
margin: 0;
}
.col p{
width: 50%;
display: block;
float:left;
line-height: 2rem;
padding: 0px;
margin: 0;
}
}
<div class=container>
<div class="desktop" aria-hidden="true">
<h3>09:00 - 11:00</h3>
<h3>11:00 - 13:00</h3>
<h3>13:00 - 15:00</h3>
<h3>15:00 - 17:00</h3>
</div>
<div class="col">
<h2>Monday</h2>
<h3>09:00 - 11:00</h3>
<p>Closed</p>
<h3>11:00 - 13:00</h3>
<p>Open</p>
<h3>13:00 - 15:00</h3>
<p>Open</p>
<h3>15:00 - 17:00</h3>
<p>Closed</p>
</div>
<div class="col">
<h2>Tuesday</h2>
<h3>09:00 - 11:00</h3>
<p>Open</p>
<h3>11:00 - 13:00</h3>
<p>Open</p>
<h3>13:00 - 15:00</h3>
<p>Open</p>
<h3>15:00 - 17:00</h3>
<p>Closed</p>
</div>
<div class="col">
<h2>Wednesday</h2>
<h3>09:00 - 11:00</h3>
<p>Open</p>
<h3>11:00 - 13:00</h3>
<p>Closed</p>
<h3>13:00 - 15:00</h3>
<p>Open</p>
<h3>15:00 - 17:00</h3>
<p>Closed</p>
</div>
<div class="col">
<h2>Thursday</h2>
<h3>09:00 - 11:00</h3>
<p>Closed</p>
<h3>11:00 - 13:00</h3>
<p>Closed</p>
<h3>13:00 - 15:00</h3>
<p>Closed</p>
<h3>15:00 - 17:00</h3>
<p>Open</p>
</div>
<div class="col">
<h2>Friday</h2>
<h3>09:00 - 11:00</h3>
<p>Closed</p>
<h3>11:00 - 13:00</h3>
<p>Closed</p>
<h3>13:00 - 15:00</h3>
<p>Closed</p>
<h3>15:00 - 17:00</h3>
<p>Open</p>
</div>
</div>
Option 4. Use another method (i.e. the suggestion by G-Cyrillus) and use WAI-ARIA to maintain the accessibility.
I really liked how clean the method that user G-Cyrillus suggested is, in terms of separation of concerns. CSS for visuals, HTML for semantics, it is what I aim for every day.
If it wasn't for this browser bug (which now that I think about it may not be a bug due to what I said earlier about why you can't use display properties on tables) this solution would be perfect (well also 90% coverage on display:contents is a little low for my liking, but that is due to the space I operate in and I am sure there would be a suitable poly-fill for the rubbish browsers!)
For this method the solution would be to utilise WAI-ARIA.
This allows for attributes to be added to elements to add further information or describe the semantics of an element to a screen reader. This would help us replace the semantics information that gets destroyed once you apply position
or display
styling to a table.
For tables we have several aria items we can use to create a table from non-table elements.
The only real downside of this method is the fact that you would either have to use JavaScript to add the aria attributes (less preferable) or add them in the HTML markup (better as then even if JavaScript fails it is still perfectly accessible).
So which method would I choose?
I am quite surprised I am saying this but option 1. "Create a second table for mobile view and switch them out using media queries."
Maintainability wise I would assume you would build this programatically linked to a CMS or similar so I don't think that is an issue. It is accessible (assuming table 2 is correctly structured) and it requires minimal CSS.
Other than my gut reaction of this being a hack, I actually think it is a great option and it would be the most accessible way of doing this.
When I get chance to put all the fiddles together you can choose for yourself but I hope this gives you some inspiration in the mean time.