Collapsed <table /> columns leave visible artifacts behind

589 views Asked by At

According to the documentation we can use visibility: collapse on a <col /> element to collapse or fold all associated columns.

But (in Chrome and Edge) I noticed that some elements remain visible, floating somewhere along the area that the collapsed columns would occupy. I could narrow it down to elements with position: absolute or position: relative. This seems to be a bug in Chrome and Edge (both show this behavior). In Firefox no unexpectedly visible artifacts remain.

const toggle = () => {
  const table = document.querySelector("#my-table");
  table.classList.toggle("collapsed");
};
.table.collapsed .collapsible-column {
  visibility: collapse;
}

.oops {
  position: relative;
}
<table id="my-table" class="table">
  <colgroup>
    <col span="2" class="collapsible-column" />
    <col span="2" class="static-column" />
  </colgroup>
  <thead>
    <tr>
      <th scope="col">col 1<br /><span class="oops">oops</span></th>
      <th scope="col">col 2</th>
      <th scope="col">col 3</th>
      <th scope="col">col 4</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>cell 1,1</td><td>cell 2,1</td><td>cell 3,1</td><td>cell 4,1</td>
    </tr>
    <tr>
      <td>cell 1,2</td>
      <td>cell 2,2<br /><span class="oops">oops</span></td>
      <td>cell 3,2</td>
      <td>cell 4,2</td>
    </tr>
  </tbody>
</table>

<button onclick="toggle()">toggle columns</button>

Is this a known bug or limitation? Do you know any workarounds? How can I ensure to also hide those elements when the column is collapsed?

Addendum: hitherto discovered workarounds

  • content from table cells with overflow: hidden does not leak from collapsed columns

    th, td { overflow: hidden; }
    

    But this is only viable if all content is meant to always stay inside the cell boundaries, and quite useless for my use case which involves an absolute positioned dropdown that needs to draw outside the cell boundaries. We could enable visible overflow for as long as the cell is interacted with via :hover and :focus-within but this is not a clean solution and not watertight: when the columns are collapsed while the focus is within a cell the respective content does still leak.

    th:focus-within, th:hover, td:focus-within, td:hover { overflow: visible; }
    
  • We could add .collapsible-cell classes to all cells that are part of collapsible coulmns and explicitly switch their content's visibility accordingly. But this approach defeats the purpose of the <col /> visibility feature, requires markup changes, and creates an explicit dependency between cell markup and colgroup definitions.

    .table.collapsed .collapsible-cell > * { display: none; }
    
1

There are 1 answers

0
jeremy-denis On

visibility: collapse; only work on table element

on firefox it's look like element inside td element are implicitly considered as part of the table element

In chrome and edge it's not the case and the position: relative "break" this consideration and make .oops is not considered as a table element

one workaround can be to explicitly set .oops to position table-row or table-cellto let this behavior work on all browser

  .oops {
      position: table-cell;
    }

const toggle = () => {
  const table = document.querySelector("#my-table");
  table.classList.toggle("collapsed");
};
.table.collapsed .collapsible-column {
  visibility: collapse;
}

.oops {
  position: table-cell;
}
<table id="my-table" class="table">
  <colgroup>
    <col span="2" class="collapsible-column" />
    <col span="2" class="static-column" />
  </colgroup>
  <thead>
    <tr>
      <th scope="col">col 1<br /><span class="oops">oops</span></th>
      <th scope="col">col 2</th>
      <th scope="col">col 3</th>
      <th scope="col">col 4</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>cell 1,1</td>
      <td>cell 2,1</td>
      <td>cell 3,1</td>
      <td>cell 4,1</td>
    </tr>
    <tr>
      <td>cell 1,2</td>
      <td>cell 2,2<br /><span class="oops">oops</span></td>
      <td>cell 3,2</td>
      <td>cell 4,2</td>
    </tr>
  </tbody>
</table>

<button onclick="toggle()">toggle columns</button>

If you want to keep position: relative on chrome and edge browser you will have to use display: none that hide non table element

const toggle = () => {
  const table = document.querySelector("#my-table");
  table.classList.toggle("collapsed");
};
.table.collapsed .collapsible-column {
  visibility: collapse;
}

.oops {
  position: relative;
}

.collapsed .oops {
  display: none;
}
<table id="my-table" class="table">
  <colgroup>
    <col span="2" class="collapsible-column" />
    <col span="2" class="static-column" />
  </colgroup>
  <thead>
    <tr>
      <th scope="col">col 1<br /><span class="oops">oops</span></th>
      <th scope="col">col 2</th>
      <th scope="col">col 3</th>
      <th scope="col">col 4</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>cell 1,1</td>
      <td>cell 2,1</td>
      <td>cell 3,1</td>
      <td>cell 4,1</td>
    </tr>
    <tr>
      <td>cell 1,2</td>
      <td>cell 2,2<br /><span class="oops">oops</span></td>
      <td>cell 3,2</td>
      <td>cell 4,2</td>
    </tr>
  </tbody>
</table>

<button onclick="toggle()">toggle columns</button>