Search code examples
htmlcsshtml-table

CSS to display table with one row per cell on narrow screens


I have HTML that looks a bit like this

<html><body><table>
  <thead><tr><th>A</th><th>B</th><th>C</th><th>D</th><th>E</th></tr></thead>
  <tbody>
    <tr><td>1A</td><td>1B</td><td>1C</td><td>1D</td><td>1E</td></tr>
    <tr><td>2A</td><td>2B</td><td>2C</td><td>2D</td><td>2E</td></tr>
    <tr><td>3A</td><td>3B</td><td>3C</td><td>3D</td><td>3E</td></tr>
  </tbody>
</table></body></html>

Each row represents an object, and the columns are its properties. The cell values are rather longer in reality – sometimes even 20–30 characters long. It renders fine on most devices, but can be problematic on smaller phones. I know I can suppress certain columns on narrow devices, but that doesn’t feel right in this particular instance. Currently I have x-overflow: scroll on the table, but I would prefer to have some CSS that causes the table to render more like this on narrow devices:

A: 1A
B: 1B
C: 1C
D: 1D
E: 1E
-----
A: 2A
B: 2B
C: 2C
D: 2D
E: 2E
-----
(etc)

I understand how to test for device width, so for the purpose of this question, let’s assume I want to do this unconditionally. Changing the HTML markup a bit is okay, e.g. to add extra classes or attributes, though I’d like to keep it using a <table> element with one <tr> per logical row. This is for backwards compatibility as I know quite a few users scrape the site (despite the existence of a much cleaner API). Due to internal politics, I’d prefer to avoid a solution that requires Javascript, if possible (and in any case, I know how to write a JS solution if need be).

I can get part way there with this, but I’m not sure how to start with the field names which are just hardcoded to ‘X’ here:

  thead { display: none; }
  table, tr, td { display: block; }
  tr + tr { border-top: thin solid black; }
  td::before { content: "X:"; display: inline-block; width: 2em; }

I’ve seen lots of sites do this sort of thing, but at the moment I’m struggling to find one that does it in CSS. It seems like it should be a frequently asked question, but I’m struggling to know what terms to search for, so may be missing some good explanations, here or elsewhere.


Solution

  • You're almost there. Might use e.g. data-col attributes on <td> elements and refer them in CSS content on :before

    thead {
      display: none;
    }
    
    table,
    tr,
    td {
      display: block;
    }
    
    tr+tr {
      border-top: thin solid black;
    }
    
    td::before {
      content: attr(data-col)':';
      display: inline-block;
      width: 2em;
    }
    <table>
      <thead>
        <tr>
          <th>A</th>
          <th>B</th>
          <th>C</th>
          <th>D</th>
          <th>E</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td data-col="A">1A</td>
          <td data-col="B">1B</td>
          <td data-col="C">1C</td>
          <td data-col="D">1D</td>
          <td data-col="E">1E</td>
        </tr>
        <tr>
          <td data-col="A">2A</td>
          <td data-col="B">2B</td>
          <td data-col="C">2C</td>
          <td data-col="D">2D</td>
          <td data-col="E">2E</td>
        </tr>
        <tr>
          <td data-col="A">3A</td>
          <td data-col="B">3B</td>
          <td data-col="C">3C</td>
          <td data-col="D">3D</td>
          <td data-col="E">3E</td>
        </tr>
      </tbody>
    </table>

    Another approach using CSS variables to store cols, less repetitive, so may save you some bytes on longer tables:

    thead {
      display: none;
    }
    
    table,
    tr,
    td {
      display: block;
    }
    
    tr+tr {
      border-top: thin solid black;
    }
    
    td:before {
      display: inline-block;
      width: 2em;
    }
    
    td:nth-child(1):before {
      content: var(--col1)':';
    }
    
    td:nth-child(2):before {
      content: var(--col2)':';
    }
    
    td:nth-child(3):before {
      content: var(--col3)':';
    }
    
    td:nth-child(4):before {
      content: var(--col4)':';
    }
    
    td:nth-child(5):before {
      content: var(--col5)':';
    }
    <table style="--col1:'A'; --col2:'B'; --col3:'C'; --col4:'D'; --col5:'E'">
      <thead>
        <tr>
          <th>A</th>
          <th>B</th>
          <th>C</th>
          <th>D</th>
          <th>E</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>1A</td>
          <td>1B</td>
          <td>1C</td>
          <td>1D</td>
          <td>1E</td>
        </tr>
        <tr>
          <td>2A</td>
          <td>2B</td>
          <td>2C</td>
          <td>2D</td>
          <td>2E</td>
        </tr>
        <tr>
          <td>3A</td>
          <td>3B</td>
          <td>3C</td>
          <td>3D</td>
          <td>3E</td>
        </tr>
      </tbody>
    </table>