Search code examples
htmlcssbootstrap-4z-indexsticky

Bootstrap Sticky column in table z-index issue


I have the below code where the first column has a position "sticky" assigned to it. The issue I am having is that the Bootstrap 4 Dropdown when open appears under the bottom column.

Example below:

CSS code where the position: sticky; is applied to the first column.

td {
  min-width: 160px;
}

th:first-child,
td:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background-color: #ff0000;
}

.table-container {
  width: 100%;
  overflow-x: scroll;
}

table {
  width: 200%;
}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

<div class="container-fluid">
  <div class="table-container">
    <table class="table table-striped table-bordered">
      <thead>
        <tr>
          <th>Column 1</th>
          <th>Column 2</th>
          <th>Column 3</th>
          <th>Column 4</th>
          <th>Column 5</th>
          <th>Column 6</th>
          <th>Column 7</th>
          <th>Column 8</th>
          <th>Column 9</th>
          <th>Column 10</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>
            <div class="dropdown">
              <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</button>
              <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
                <a class="dropdown-item" href="#">Action</a>
                <a class="dropdown-item" href="#">Another action</a>
                <a class="dropdown-item" href="#">Something else here</a>
              </div>
            </div>
          </td>
          <td>Row 1, Column 2</td>
          <td>Row 1, Column 3</td>
          <td>Row 1, Column 4</td>
          <td>Row 1, Column 5</td>
          <td>Row 1, Column 6</td>
          <td>Row 1, Column 7</td>
          <td>Row 1, Column 8</td>
          <td>Row 1, Column 9</td>
          <td>Row 1, Column 10</td>
        </tr>
        <tr>
          <td>
            <div class="dropdown">
              <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</button>
              <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
                <a class="dropdown-item" href="#">Action</a>
                <a class="dropdown-item" href="#">Another action</a>
                <a class="dropdown-item" href="#">Something else here</a>
              </div>
            </div>
          </td>
          <td>Row 2, Column 2</td>
          <td>Row 2 Column 3</td>
          <td>Row 2, Column 4</td>
          <td>Row 2, Column 5</td>
          <td>Row 2, Column 6</td>
          <td>Row 2, Column 7</td>
          <td>Row 2, Column 8</td>
          <td>Row 2, Column 9</td>
          <td>Row 2, Column 10</td>
        </tr>
        <tr>
          <td>
            <div class="dropdown">
              <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</button>
              <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
                <a class="dropdown-item" href="#">Action</a>
                <a class="dropdown-item" href="#">Another action</a>
                <a class="dropdown-item" href="#">Something else here</a>
              </div>
            </div>
          </td>
          <td>Row 3, Column 2</td>
          <td>Row 3, Column 3</td>
          <td>Row 3, Column 4</td>
          <td>Row 3, Column 5</td>
          <td>Row 3, Column 6</td>
          <td>Row 3, Column 7</td>
          <td>Row 3, Column 8</td>
          <td>Row 3, Column 9</td>
          <td>Row 3, Column 10</td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct" crossorigin="anonymous"></script>

I've tried adding a higher z-index to the drop-down but it seems like it gets ignored.


Solution

  • This happens because each of the sticky elements are their own stacking context, and any z-index applied to the dropdown elements would be relative to elements within the same stacking context of the td:first-child or th:first-child.

    To workaround this, we'd need to promote the td:first-child or th:first-child element to a higher z-index when its dropdown is open. We could use :has() like:

    :first-child:is(th, td):has(.dropdown.show) {
      z-index: 2;
    }
    

    td {
      min-width: 160px;
    }
    
    th:first-child,
    td:first-child {
      position: sticky;
      left: 0;
      z-index: 1;
      background-color: #ff0000;
    }
    
    .table-container {
      width: 100%;
      overflow-x: scroll;
    }
    
    table {
      width: 200%;
    }
    
    
    :first-child:is(th, td):has(.dropdown.show) {
      z-index: 2;
    }
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    
    <div class="container-fluid">
      <div class="table-container">
        <table class="table table-striped table-bordered">
          <thead>
            <tr>
              <th>Column 1</th>
              <th>Column 2</th>
              <th>Column 3</th>
              <th>Column 4</th>
              <th>Column 5</th>
              <th>Column 6</th>
              <th>Column 7</th>
              <th>Column 8</th>
              <th>Column 9</th>
              <th>Column 10</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>
                <div class="dropdown">
                  <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</button>
                  <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
                    <a class="dropdown-item" href="#">Action</a>
                    <a class="dropdown-item" href="#">Another action</a>
                    <a class="dropdown-item" href="#">Something else here</a>
                  </div>
                </div>
              </td>
              <td>Row 1, Column 2</td>
              <td>Row 1, Column 3</td>
              <td>Row 1, Column 4</td>
              <td>Row 1, Column 5</td>
              <td>Row 1, Column 6</td>
              <td>Row 1, Column 7</td>
              <td>Row 1, Column 8</td>
              <td>Row 1, Column 9</td>
              <td>Row 1, Column 10</td>
            </tr>
            <tr>
              <td>
                <div class="dropdown">
                  <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</button>
                  <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
                    <a class="dropdown-item" href="#">Action</a>
                    <a class="dropdown-item" href="#">Another action</a>
                    <a class="dropdown-item" href="#">Something else here</a>
                  </div>
                </div>
              </td>
              <td>Row 2, Column 2</td>
              <td>Row 2 Column 3</td>
              <td>Row 2, Column 4</td>
              <td>Row 2, Column 5</td>
              <td>Row 2, Column 6</td>
              <td>Row 2, Column 7</td>
              <td>Row 2, Column 8</td>
              <td>Row 2, Column 9</td>
              <td>Row 2, Column 10</td>
            </tr>
            <tr>
              <td>
                <div class="dropdown">
                  <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</button>
                  <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
                    <a class="dropdown-item" href="#">Action</a>
                    <a class="dropdown-item" href="#">Another action</a>
                    <a class="dropdown-item" href="#">Something else here</a>
                  </div>
                </div>
              </td>
              <td>Row 3, Column 2</td>
              <td>Row 3, Column 3</td>
              <td>Row 3, Column 4</td>
              <td>Row 3, Column 5</td>
              <td>Row 3, Column 6</td>
              <td>Row 3, Column 7</td>
              <td>Row 3, Column 8</td>
              <td>Row 3, Column 9</td>
              <td>Row 3, Column 10</td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct" crossorigin="anonymous"></script>

    But :has() is not compatible in all major browsers yet, so we would need to implement this a slightly different way with JavaScript, where we detect an open dropdown and elevate the z-index as appropriate, like:

    const observer = new MutationObserver(
      (entries) =>
        entries.forEach(({ target }) => {
          target.closest(':first-child:is(th, td)').style.zIndex =
            target.classList.contains('show') ? '2' : '';
        }),
    );
    
    document
      .querySelectorAll('.dropdown')
      .forEach((dropdown) => {
        const thTdFirst = dropdown.closest(':first-child:is(th, td)');
        if (thTdFirst) {
          observer.observe(dropdown, { attributeFilter: ['class'] });
        }
      });
    td {
      min-width: 160px;
    }
    
    th:first-child,
    td:first-child {
      position: sticky;
      left: 0;
      z-index: 1;
      background-color: #ff0000;
    }
    
    .table-container {
      width: 100%;
      overflow-x: scroll;
    }
    
    table {
      width: 200%;
    }
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    
    <div class="container-fluid">
      <div class="table-container">
        <table class="table table-striped table-bordered">
          <thead>
            <tr>
              <th>Column 1</th>
              <th>Column 2</th>
              <th>Column 3</th>
              <th>Column 4</th>
              <th>Column 5</th>
              <th>Column 6</th>
              <th>Column 7</th>
              <th>Column 8</th>
              <th>Column 9</th>
              <th>Column 10</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>
                <div class="dropdown">
                  <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</button>
                  <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
                    <a class="dropdown-item" href="#">Action</a>
                    <a class="dropdown-item" href="#">Another action</a>
                    <a class="dropdown-item" href="#">Something else here</a>
                  </div>
                </div>
              </td>
              <td>Row 1, Column 2</td>
              <td>Row 1, Column 3</td>
              <td>Row 1, Column 4</td>
              <td>Row 1, Column 5</td>
              <td>Row 1, Column 6</td>
              <td>Row 1, Column 7</td>
              <td>Row 1, Column 8</td>
              <td>Row 1, Column 9</td>
              <td>Row 1, Column 10</td>
            </tr>
            <tr>
              <td>
                <div class="dropdown">
                  <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</button>
                  <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
                    <a class="dropdown-item" href="#">Action</a>
                    <a class="dropdown-item" href="#">Another action</a>
                    <a class="dropdown-item" href="#">Something else here</a>
                  </div>
                </div>
              </td>
              <td>Row 2, Column 2</td>
              <td>Row 2 Column 3</td>
              <td>Row 2, Column 4</td>
              <td>Row 2, Column 5</td>
              <td>Row 2, Column 6</td>
              <td>Row 2, Column 7</td>
              <td>Row 2, Column 8</td>
              <td>Row 2, Column 9</td>
              <td>Row 2, Column 10</td>
            </tr>
            <tr>
              <td>
                <div class="dropdown">
                  <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</button>
                  <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
                    <a class="dropdown-item" href="#">Action</a>
                    <a class="dropdown-item" href="#">Another action</a>
                    <a class="dropdown-item" href="#">Something else here</a>
                  </div>
                </div>
              </td>
              <td>Row 3, Column 2</td>
              <td>Row 3, Column 3</td>
              <td>Row 3, Column 4</td>
              <td>Row 3, Column 5</td>
              <td>Row 3, Column 6</td>
              <td>Row 3, Column 7</td>
              <td>Row 3, Column 8</td>
              <td>Row 3, Column 9</td>
              <td>Row 3, Column 10</td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct" crossorigin="anonymous"></script>