Search code examples
htmlcsshtml-table

How to have line-breaks in a table cell only after a maximum width in CSS


I have a table with cells that could have long content. I want to display the content without line breaks if it does not exceed a certain width.

If the table exceeds the width of the parent element, the default behaviour is to break everything as soon as possible inside the table-cell. The cell takes the width of the longest word in the column.

If I use white-space: nowrap; it does not break at all. If I apply max-width: 60px; the cell does not extend up to that width. table-layout: fixed; does also not work, as the width of the table should expand if needed. min-width: 60px; is also not what I want, because I want the columns as short as possible if the text is not long.

This is the default behaviour:

This | LongerWord | Normal |
is a |            |        |
long |            |        |
text |            |        |

And I would like to have something like this:

This is a | LongerWord | Normal |
long cell |            |        |

In the following example I would like to display all cells but the very long one in just one line. The long one should break after a certain length, for example after 350px, which would be after should. The second row should all be in one line:

td {
  border: 1px solid black;
  /*white-space: nowrap;*/
  /*max-width: 350px;*/
  /*min-width: 350px;*/
}
<table>
<tr>
<td>This is a long text</td>
<td>short</td>
<td>LongerWord</td>
<td>Normal</td>
<td>This is a long text</td>
<td>Sometimes very long text could appear that should break so the table does not get way too wide</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>Normal</td>
</tr>
<tr>
<td>short</td>
<td>short</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>This is a long text</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>Normal</td>
</tr>
</table>

I can find a solution with JavaScript, but is this possible to do in pure CSS?

for (const td of document.querySelectorAll('td')) {
  if (td.clientWidth > 350) {
    td.style.minWidth = '350px';
    td.style.whiteSpace = 'normal';
  }
}
td {
  border: 1px solid black;
  white-space: nowrap;
  vertical-align: top;
}
<table>
<tr>
<td>This is a long text</td>
<td>short</td>
<td>LongerWord</td>
<td>Normal</td>
<td>This is a long text</td>
<td>Sometimes very long text could appear that should break so the table does not get way too wide</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>Normal</td>
</tr>
<tr>
<td>short</td>
<td>short</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>This is a long text</td>
<td>LongerWord</td>
<td>LongerWord</td>
<td>Normal</td>
</tr>
</table>

NOTE: It seems it is not really possible to tell the table layout rendering in the browser to shorten cells only in specific cases. But I am not sure about this.


Solution

  • Short Answer

    What you want is not possible. You cannot keep the current HTML, add a max width to a table cell, keep table cells at their minimum widths and have words break without using JavaScript.

    There are however some good alternatives that apply to most of your requirements.

    Option 1 - overflow within a cell.

    By applying white-space: nowrap and overflow-y you can use max-width. The down side is that you have to scroll in the cell.

    td {
       white-space:nowrap;
       border: 1px solid black;
       max-width: 350px;
       overflow-y:hidden;
    }
    <table>
    <tr>
    <td>This is a long text</td>
    <td>short</td>
    <td>LongerWord</td>
    <td>Normal</td>
    <td>This is a long text</td>
    <td>Sometimes very long text could appear that should break so the table does not get way too wide</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>Normal</td>
    </tr>
    <tr>
    <td>short</td>
    <td>short</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>This is a long text</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>Normal</td>
    </tr>
    </table>

    Option 2 - Minor markup change

    If you can apply a class to the ones you know are too long then this becomes trivial. Obviously this assumes you have static data (at which point you would probably just set each column width precisely anyway).

    td {
       white-space:nowrap;
       border: 1px solid black;
       max-width: 350px;
       overflow-y:hidden;
    }
    .long{
       white-space: normal;
       min-width: 350px;
    }
    <table>
    <tr>
    <td>This is a long text</td>
    <td>short</td>
    <td>LongerWord</td>
    <td>Normal</td>
    <td>This is a long text</td>
    <td class="long">Sometimes very long text could appear that should break so the table does not get way too wide</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>Normal</td>
    </tr>
    <tr>
    <td>short</td>
    <td>short</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>This is a long text</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>Normal</td>
    </tr>
    </table>

    Option 3 - some super light weight JavaScript magic

    If you combine our previous example (adding a class to a table cell) with a quick check if a table cell is too large we get a solution that is light-weight and will work with dynamic data / without having to worry about breaking things if you change data.

    var maxWidth = 350;
    var tableCells = document.querySelectorAll('td');
    
    for(x = 0; x < tableCells.length; x++){
       if(tableCells[x].clientWidth > 350){ //please note if you want this to include the border width then you need to use tableCells[x]..offsetWidth to include the border.
          tableCells[x].classList.add("long");
       }
    }
    td {
       white-space:nowrap;
       border: 1px solid black;
       max-width: 350px;
       overflow-y:hidden;
    }
    .long{
       white-space: normal;
       min-width: 350px;
    }
    <table>
    <tr>
    <td>This is a long text</td>
    <td>short</td>
    <td>LongerWord</td>
    <td>Normal</td>
    <td>This is a long text</td>
    <td>Sometimes very long text could appear that should break so the table does not get way too wide</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>Normal</td>
    </tr>
    <tr>
    <td>short</td>
    <td>short</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>This is a long text</td>
    <td>LongerWord</td>
    <td>LongerWord</td>
    <td>Normal</td>
    </tr>
    </table>

    Option 4 - change your HTML

    If you can change your HTML (i.e. this isn't tabular data) then grouping items by column in divs makes this easy to solve. I am not going to do an example for this as I have a feeling this would not be an option.

    Option 5 - do the work server side

    You could count the characters on the server and set the CSS class server side if you REALLY want to avoid JavaScript (so add class="long" on the server based on character count).

    Although this won't be exact it should be as close and allow you to have dynamic data without any JavaScript.

    Conclusion

    Someone needs to invent a CSS property to fix this as it is a real pain!

    I would go for option number 3, it is clean, easy to implement into an existing solution and easy to maintain. It also beats trying to add inline styles via JavaScript as it will still comply with a Content Security Policy. At the end of the day the table will still display if the JavaScript fails.