Home > Software engineering >  How to make a table display rows in two columns?
How to make a table display rows in two columns?

Time:08-01

I have a HTML table that currently looks like this:

enter image description here

I need to use CSS to make the table rows display as two columns, so it looks like this: enter image description here

(The font style/border width does not need to change)

However, I can't edit any of the HTML, as I do not have access to it. I can only alter the CSS.

I found this tutorial on W3 Schools which shows how to make a two-column layout using flexbox properties. So I applied it to my table as follows:

table {
  display:block;
}

tr {
  display: flex;
  border: 1px solid black;
}

td {
  flex:50%;
}

But it doesn't seemed to have worked. It just stays in one column. I have made a codepen here.

Any ideas how I can make the table go into two columns (it doesn't have to be via flexbox but it should only be via CSS as I can't change the HTML).

Please note: This project will not be a website, but actually printed out as a PDF. So I am not too bothered about best practices for compatibility with multiple browsers or mobile etc. Because the final output will be PDF. Normally I would just use DTP software to sort out the layout. But in those case, there's too many entries. So if I can do the layout at the CSS level that will be better.

CodePudding user response:

It works at width: 40%; as well. Hope it helps. Thanks

    * {
      box-sizing: border-box;
    }

    tr {
       float: left;
       width: 50%;
       border: 1px solid black;
    }

    tbody:after {
      content: "";
      display: table;
      clear: both;
    }

CodePudding user response:

All it takes is flex on the parent tbody, and making its children (tr) width 50%.

tbody {
  display: flex;
  flex-wrap: wrap;
}

tr {
  box-sizing: border-box;
  width: 50%;
}


/* borders */

table {
  border-top: 1px solid black;
  border-right: 1px solid black;
  border-left: 1px solid black;
}

tr {
  border-bottom: 1px solid black;
}

tr:nth-child(even) {
  border-left: 1px solid black;
}
<table>
  <tbody>
    <tr>
      <td>1. Abel Obrien</td>
    </tr>
    <tr>
      <td>2. Marylou Kelley</td>
    </tr>
    <tr>
      <td>3. Dusty Banks</td>
    </tr>
    <tr>
      <td>4. Marcellus Richard</td>
    </tr>
    <tr>
      <td>5. Elvin Porter</td>
    </tr>
    <tr>
      <td>6. Savannah Mullen</td>
    </tr>
    <tr>
      <td>7. Edna Bean</td>
    </tr>
    <tr>
      <td>8. Carson Larsen</td>
    </tr>
    <tr>
      <td>9. Kelli Mack</td>
    </tr>
    <tr>
      <td>10. Valeria Harding</td>
    </tr>
  </tbody>
</table>

CodePudding user response:

Solutions

CSS Grid (preferred)

Since every <tr> only ever has one <td>, you can simply use CSS Grid:

tbody {
  display: grid;
  grid-template-columns: repeat(2, auto);
}

/* Borders */
/* The margin declarations are used to simulate border-collapse:collapse */
tr {
  margin: 0 0 -1px -1px; 
  border: 1px solid black;
}
tbody:not(:empty) {
  margin: 0 0 1px 1px;
}
<table>
  <tbody>
    <tr>
      <td>1. Abel Obrien</td>
    </tr>
    <tr>
      <td>2. Marylou Kelley</td>
    </tr>
    <tr>
      <td>3. Dusty Banks</td>
    </tr>
    <tr>
      <td>4. Marcellus Richard</td>
    </tr>
    <tr>
      <td>5. Elvin Porter</td>
    </tr>
    <tr>
      <td>6. Savannah Mullen</td>
    </tr>
    <tr>
      <td>7. Edna Bean</td>
    </tr>
    <tr>
      <td>8. Carson Larsen</td>
    </tr>
    <tr>
      <td>9. Kelli Mack</td>
    </tr>
    <tr>
      <td>10. Valeria Harding</td>
    </tr>
  </tbody>
</table>

More general solution

If the rows could have included more than one <td> each, you would (probably) have to resort to display: contents:

tbody {
  display: grid;
  grid-template-columns: repeat(2, auto);
}
tr {display: contents}

/* Borders */
td {
  margin: 0 0 -1px -1px;
  border: 1px solid black;
}
tbody:not(:empty) {
  margin: 0 0 1px 1px;
}
<table>
  <tbody>
    <tr>
      <td>1. Some Example</td>
    </tr>
    <tr>
      <td>2. Other Example</td>
      <td>3. A third Example</td>
    </tr>
    <tr>
      <td>4. Yet another Example</td>
    </tr>
  </tbody>
</table>

Note: In the example with display: contents, we lose the default styles (namely the padding) of the rows because we are now declaring the border to the data cells instead of the rows.

CSS Flex

We can also use CSS Flexbox to lay out the cells.

Note: Use CSS Grid for grid-like results, and CSS Flexbox for flow-like results. You seem to expect a grid-like result.

Declaring tbody to be a flex parent (with allowed wrapping) gives us a flow-like result:

tbody {
  display: flex;
  flex-wrap: wrap;
}

/* Borders */
tr {
  margin: 0 0 -1px -1px;
  border: 1px solid black;
}
tbody:not(:empty) {
  margin: 0 0 1px 1px;
}
<table>
  <tbody>
    <tr>
      <td>1. Abel Obrien</td>
    </tr>
    <tr>
      <td>2. Marylou Kelley</td>
    </tr>
    <tr>
      <td>3. Dusty Banks</td>
    </tr>
    <tr>
      <td>4. Marcellus Richard</td>
    </tr>
    <tr>
      <td>5. Elvin Porter</td>
    </tr>
    <tr>
      <td>6. Savannah Mullen</td>
    </tr>
    <tr>
      <td>7. Edna Bean</td>
    </tr>
    <tr>
      <td>8. Carson Larsen</td>
    </tr>
    <tr>
      <td>9. Kelli Mack</td>
    </tr>
    <tr>
      <td>10. Valeria Harding</td>
    </tr>
  </tbody>
</table>

Now we can try to force this flow-like result into a grid-like, 2-column layout (which CSS Flexbox is not intended for; use CSS Grid if you want a grid-like result):

  • Using calc():

tr {
  width: calc(
    50%
    -1px /* Subtract the overlappable border side */
  );
}

tbody {
  display: flex;
  flex-wrap: wrap;
}

/* Borders */
tr {
  margin: 0 0 -1px -1px;
  border: 1px solid black;
}
tbody:not(:empty) {
  margin: 0 0 1px 1px;
}
<table>
  <tbody>
    <tr>
      <td>1. Extensively long name that may or may not fit within this cell</td>
    </tr>
    <tr>
      <td>2. Marylou Kelley</td>
    </tr>
    <tr>
      <td>3. Dusty Banks</td>
    </tr>
    <tr>
      <td>4. Marcellus Richard</td>
    </tr>
    <tr>
      <td>5. Elvin Porter</td>
    </tr>
    <tr>
      <td>6. Savannah Mullen</td>
    </tr>
    <tr>
      <td>7. Edna Bean</td>
    </tr>
    <tr>
      <td>8. Carson Larsen</td>
    </tr>
    <tr>
      <td>9. Kelli Mack</td>
    </tr>
    <tr>
      <td>10. Valeria Harding</td>
    </tr>
  </tbody>
</table>

  • Using flex:

tr {
  width: 50%;
  box-sizing: border-box; /* Subtracts both border-sides */
  flex-grow: 1; /* Adds the non-overlappable border-side */
}
tbody {
  display: flex;
  flex-wrap: wrap;
}

/* Borders */
tr {
  margin: 0 0 -1px -1px;
  border: 1px solid black;
}
tbody:not(:empty) {
  margin: 0 0 1px 1px;
}
<table>
  <tbody>
    <tr>
      <td>1. Extensively long name that may or may not fit within this cell</td>
    </tr>
    <tr>
      <td>2. Marylou Kelley</td>
    </tr>
    <tr>
      <td>3. Dusty Banks</td>
    </tr>
    <tr>
      <td>4. Marcellus Richard</td>
    </tr>
    <tr>
      <td>5. Elvin Porter</td>
    </tr>
    <tr>
      <td>6. Savannah Mullen</td>
    </tr>
    <tr>
      <td>7. Edna Bean</td>
    </tr>
    <tr>
      <td>8. Carson Larsen</td>
    </tr>
    <tr>
      <td>9. Kelli Mack</td>
    </tr>
    <tr>
      <td>10. Valeria Harding</td>
    </tr>
  </tbody>
</table>

Regarding W3 Schools' trick

The reason why the W3 Schools "2 column" trick doesn't work is because it requires a specific HTML structure. More specifically, it requires the two columns to be the only children in a wrapper. Each such column would have to already contain the children to lay out.

In your HTML, the <td> elements are the elements we want to lay out in the end. Because every <tr> element only contains one <td> element, they can effectively be treated as the elements we want to lay out.

These <td> elements we want to lay out (and therefore their <tr> parent elements) are only contained in a single table section (<tbody>), not in multiple distinct "column" elements. This means, translating W3 Schools' trick to your HTML does not work, because the HTML structure is fundamentally different.

  • Related