experiment_id: web-retrieval-v1
fixture_id: /demos/web-component-grid-tree-data
mode: provenance
representation: html
origin_response_id: beafb06e8a013d690b032e5181290223
version: 2026-10-21
version_created_at_utc: 2026-10-21T20:42:35.586Z
version_nonce: a722a2789066effd98e1c30951f2e0b6
generated_at_utc: 2026-10-25T01:38:40.128Z
response_nonce: a947f450b7b5101b4c27c7f4d992ccf9

Expandable Row with Nested Grid

8 of 125
ZingGrid provides custom HTML, CSS, and JavaScript to easily create a collapsable row with a secondary nested grid in the tree.
Result Full HTML CSS JS
Edit Download

Full Code

<!DOCTYPE html>
<html class="zg-html">

<head>
  <meta charset="utf-8">
  <title>ZingGrid Demo</title>
  <script nonce="undefined" src="https://cdn.zinggrid.com/zinggrid.min.js"></script>
  <style>
    .zg-body {
      background: #fff;
    }

    zing-grid[loading] {
      height: 500px;
    }

    zing-grid zing-grid[loading] {
      height: 91px;
    }

    /** Expandable Row Trigger **/
    .zg-body .arrow {
      display: inline-block;
      transform: rotate(90deg);
      transform-origin: 0% 0%;
      cursor: pointer;
      font-size: 1.5rem;
      position: relative;
      left: 2.5rem;
      top: 0.25rem;
    }

    .zg-body .active .arrow {
      transform: rotate(180deg);
      left: 2.5rem;
      top: 1.5rem;
    }

    /** Expandable Row Header **/
    .zg-body .row--header {
      display: flex;
      align-items: center;
    }

    .zg-body .team--info {
      margin-left: 2.5rem;
    }

    /** Expandable Row CSS **/
    .zg-body zg-cell[type="custom"] {
      padding: 0;
    }

    .zg-body .default {
      border-bottom: 1px solid #ebebeb;
    }

    .zg-body zg-row:hover {
      cursor: pointer;
      background-color: #f5f7fa;
      transition: background-color 0.25s ease-in;
    }

    .zg-body .expandable {
      background: #f5f5f5;
      padding: 1rem 0;
      display: none;
      position: relative;
      overflow: hidden;
    }

    .zg-body .active .expandable {
      display: block;
    }

    /** Set padding **/
    /** NOTE: Css looks off here, but it is to fix how it looks on site **/
    .zg-body .default--arrow {
      padding: 0.9rem 0;
    }

    zing-grid[loading] {
      height: 487px;
    }
  </style>
</head>

<body class="zg-body">
  <zing-grid caption="Expandable Rows w/ Nested Grid" viewport-stop loading src="https://zinggrid-example-datasets.uc.r.appspot.com/employees">
    <zg-colgroup>
      <zg-column type="custom" header="Employees" renderer="assignDataToNestedGrid">
        <template>
          <div class="row--header">
            <div class="default  default--arrow">
              <span class="arrow">⌃</span>
            </div>
            <div class="team--info">[[record.name]] ([[record.job]])</div>
          </div>
          <div class="expandable">
            <!-- define nested column and types -->
            <zing-grid width="100%" viewport-stop>
              <zg-colgroup>
                <zg-column index="actor"></zg-column>
                <zg-column index="job"></zg-column>
                <zg-column index="age" type="number"></zg-column>
                <zg-column index="hourly_salary" type="currency"></zg-column>
                <zg-column index="origin"></zg-column>
                <zg-column index="home_town"></zg-column>
                <zg-column index="gender"></zg-column>
                <zg-column index="species"></zg-column>
              </zg-colgroup>
            </zing-grid>
          </div>
        </template>
      </zg-column>
    </zg-colgroup>
  </zing-grid>
  <script>
    ZingGrid.setLicense(['26ccbfec16b8be9ee98c7d57bee6e498']);

    function assignDataToNestedGrid(index, cellRef, recordRef) {
      // assign data to secondary zing-grid
      let zgRef = cellRef.querySelector('zing-grid');
      let data = [recordRef.record];
      zgRef.setData(JSON.stringify(data));
    }
    // window:load event for Javascript to run after HTML
    // because this Javascript is injected into the document head
    window.addEventListener('load', () => {
      // Javascript code to execute after DOM content
      const zgRef = document.querySelector('zing-grid');
      zgRef.addEventListener('row:click', bindExpandEvent);

      function bindExpandEvent(e) {
        let oDOMRow = e.detail.ZGTarget;
        oDOMRow.classList.toggle('active');
        // refresh the grid to properly size all the columns    
        oDOMRow.querySelector('zing-grid').refresh();
      }
      // open first row
      zgRef.executeOnLoad(() => {
        setTimeout(function() {
          const zgFirstRow = document.querySelector('zg-row[aria-rowindex="2"]');
          zgFirstRow.classList.toggle('active');
          zgFirstRow.querySelector('zing-grid').refresh();
        }, 200);
      });
    });
  </script>
</body>

</html>
<!DOCTYPE html>
<html class="zg-html">

<head>
  <meta charset="utf-8">
  <title>ZingGrid Demo</title>
  <script src="https://cdn.zinggrid.com/zinggrid.min.js"></script>
</head>

<body class="zg-body">
  <zing-grid caption="Expandable Rows w/ Nested Grid" viewport-stop loading
    src="https://zinggrid-example-datasets.uc.r.appspot.com/employees">
    <zg-colgroup>
      <zg-column type="custom" header="Employees" renderer="assignDataToNestedGrid">
        <template>
          <div class="row--header">
            <div class="default  default--arrow">
              <span class="arrow">⌃</span>
            </div>
            <div class="team--info">[[record.name]] ([[record.job]])</div>
          </div>
          <div class="expandable">
            <!-- define nested column and types -->
            <zing-grid width="100%" viewport-stop>
              <zg-colgroup>
                <zg-column index="actor"></zg-column>
                <zg-column index="job"></zg-column>
                <zg-column index="age" type="number"></zg-column>
                <zg-column index="hourly_salary" type="currency"></zg-column>
                <zg-column index="origin"></zg-column>
                <zg-column index="home_town"></zg-column>
                <zg-column index="gender"></zg-column>
                <zg-column index="species"></zg-column>
              </zg-colgroup>
            </zing-grid>
          </div>
        </template>
      </zg-column>
    </zg-colgroup>
  </zing-grid>
</body>

</html>
.zg-body{ background:#fff; }

zing-grid[loading] { height: 500px; }
zing-grid zing-grid[loading] { height: 91px; }

/** Expandable Row Trigger **/
.zg-body .arrow { display:inline-block; transform:rotate(90deg); transform-origin: 0% 0%; cursor:pointer; font-size:1.5rem; position:relative; left:2.5rem; top:0.25rem; }
.zg-body .active .arrow { transform:rotate(180deg); left:2.5rem; top:1.5rem; }

/** Expandable Row Header **/
.zg-body .row--header { display:flex; align-items:center; }
.zg-body .team--info { margin-left:2.5rem; }

/** Expandable Row CSS **/
.zg-body zg-cell[type="custom"] { padding:0; }
.zg-body .default { border-bottom:1px solid #ebebeb; }
.zg-body zg-row:hover { cursor:pointer; background-color:#f5f7fa; transition:background-color 0.25s ease-in; }
.zg-body .expandable { background:#f5f5f5; padding:1rem 0; display:none; position:relative; overflow:hidden; }
.zg-body .active .expandable { display:block; }

/** Set padding **/
/** NOTE: Css looks off here, but it is to fix how it looks on site **/
.zg-body .default--arrow { padding:0.9rem 0; }
function assignDataToNestedGrid(index, cellRef, recordRef) {
  // assign data to secondary zing-grid
  let zgRef = cellRef.querySelector('zing-grid');
  let data = [recordRef.record];
  zgRef.setData(JSON.stringify(data));
}
// window:load event for Javascript to run after HTML
// because this Javascript is injected into the document head
window.addEventListener('load', () => {
  // Javascript code to execute after DOM content
  const zgRef = document.querySelector('zing-grid');
  zgRef.addEventListener('row:click', bindExpandEvent);

  function bindExpandEvent(e) {
    let oDOMRow = e.detail.ZGTarget;
    oDOMRow.classList.toggle('active');
    // refresh the grid to properly size all the columns    
    oDOMRow.querySelector('zing-grid').refresh();
  }
  // open first row
  zgRef.executeOnLoad(() => {
    setTimeout(function() {
      const zgFirstRow = document.querySelector('zg-row[aria-rowindex="2"]');
      zgFirstRow.classList.toggle('active');
      zgFirstRow.querySelector('zing-grid').refresh();
    }, 200);
  });
});

Interested in this demo? Modify it to your needs in ZingSoft Studio, our testing sandbox. It's free to sign up, and you can come back and edit at any time!

Edit in Studio

Demo Gallery