<div class="user-datalist-wrapper"></div>

<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"><symbol id="arrow-right" fill="none" viewBox="0 0 24 24" strokewidth="1.5" stroke="currentColor"><path strokelinecap="round" strokelinejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"></path></symbol><symbol id="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokewidth="1.5"><path d="M4.5 12.75L10.5 18.75L19.5 5.25" stroke-linecap="round" stroke-linejoin="round"></path></symbol></svg>

<em>Resize window to switch view from grid to table.</em>

<div class="prose">
  <p>This is an experiment where I attempt to switch tabular data views from a grid view to a table view with the power of style container queries. Unfortunatly, it's not working (yet).</p>
  <p>For this experiment, I compare 3 different approaches:</p>
  <ol>
    <li><a href="https://codepen.io/mrtnvh/pen/KKxeLrX">Style container queries + HTML table (Prefered solution -> Semantic markup + CSS for visual layout)</a></li>
    <li><a href="https://codepen.io/mrtnvh/pen/yLxmEQM">Style container queries + HTML divs + CSS Grid</a></li>
    <li><a href="https://codepen.io/mrtnvh/pen/dyqxBVx">Media queries + HTML table</a></li>
  </ol>
  </p>
</div>
@layer variables, root, userdatalist, utilities;

@import url("https://unpkg.com/open-props");
@import url("https://unpkg.com/modern-css-reset@1.4.0/dist/reset.min.css");

@layer userdatalist {
  .user-datalist-wrapper {
    container-type: inline-size;
    resize: both;
    overflow: scroll;
    border: 0.25rem solid darkcyan;
  }

  .user-datalist {
    outline: 1px solid var(--stone-4);
    inline-size: 100%;

    @container (max-width: 750px) {
      display: block;
      outline-color: transparent;
    }

    thead {
      text-align: left;

      @container (max-width: 750px) {
        clip: rect(0 0 0 0);
        clip-path: inset(50%);
        height: 1px;
        overflow: hidden;
        position: absolute;
        white-space: nowrap;
        width: 1px;
      }

      th {
        padding-block: calc(var(--spacer) / 4);
      }
    }

    tbody {
      @container (max-width: 750px) {
        display: grid;
        grid-template-columns: repeat(
          auto-fill,
          minmax(calc(var(--spacer) * 13), 1fr)
        );
        gap: var(--spacer);
      }

      tr {
        padding: 0;
        vertical-align: middle;
        background-color: var(--stone-0);
        border: 1px solid transparent;

        @container (max-width: 749px) {
          display: grid;
          grid-template:
            "full-name thumbnail" auto
            "date-of-birth thumbnail" auto
            "phone thumbnail" auto
            "email actions" auto / auto calc(var(--spacer) * 3);
          padding-inline-start: var(--spacer);
          border-color: var(--stone-4);
        }

        &:nth-child(odd) {
          @container (min-width: 750px) {
            background-color: var(--stone-2);
          }
        }

        &:is(:focus-within, :has(td:hover)) {
          background-color: var(--indigo-1);
        }

        &:is(:focus-within) {
          outline: var(--outline-size) solid var(--outline-color);
          outline-offset: calc(var(--outline-size) * -1);
        }

        & :where(td) {
          padding: 0;
          overflow: hidden;
          text-overflow: ellipsis;
          line-height: var(--font-lineheight-1);

          &:last-child {
            padding-inline-end: calc(var(--spacer) / 2);
          }

          @container (max-width: 749px) {
            padding: calc(var(--spacer) / 8);
            vertical-align: middle;
            cursor: pointer;
            white-space: nowrap;

            &:first-child {
              padding-inline-start: 0;
              padding-block: 0;
            }

            &:last-child {
              text-align: center;
              padding-inline-end: calc(var(--spacer) / 1.5);
            }
          }
        }
      }
    }
  }

  :where(.user-datalist
      td:not(.user-dataitem__thumbnail, .user-dataitem__actions)) {
    @container (max-width: 749px) {
      margin-block: calc(var(--size-1) / 2);
    }
  }

  .user-dataitem__thumbnail {
    grid-area: thumbnail;

    @container (max-width: 749px) {
      background-color: var(--stone-3);
    }
  }

  .user-dataitem__image {
    inline-size: 100%;

    @container (min-width: 750px) {
      max-inline-size: none;
      inline-size: calc(var(--spacer) * 2);
      block-size: calc(var(--spacer) * 2);
    }
  }

  td.user-dataitem__full-name {
    grid-area: full-name;

    @container (max-width: 749px) {
      margin-block-start: var(--spacer);
      font-size: var(--size-5);
      font-weight: 900;
      line-height: var(--font-lineheight-0);
    }
  }

  .user-dataitem__email {
    grid-area: email;
    margin-block-end: var(--spacer);
  }

  .user-dataitem__date-of-birth {
    grid-area: date-of-birth;

    @container (max-width: 749px) {
      font-size: var(--font-size-0);
      margin-block-end: calc(var(--size-1));
      color: var(--stone-8);
    }
  }

  .user-dataitem__phone {
    grid-area: phone;
  }

  .user-dataitem__actions {
    text-align: center;

    @container (max-width: 749px) {
      grid-area: actions;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      background-color: var(--stone-3);

      &:has(tr:hover) {
        background-color: var(--indigo-3);
      }
    }
  }

  .user-dataitem__action--detail {
    display: flex;
    flex-direction: column;
    align-items: end;
    justify-content: center;
    outline: none;
  }
}

@layer variables {
  /* Variables */
  :root {
    --brand-primary: var(--indigo-8);

    --spacer: clamp(1.5rem, 3vw, 2rem);
    --container-spacer: clamp(1rem, 6vw, 4.5rem);
    --shadow-strength: 2.5%;

    --outline-size: 0.125rem;
    --outline-color: var(--indigo-6);
    --outline-offset: 0.25rem;
  }
}

@layer root {
  :root {
    font-size: clamp(15px, 1.5vw, 18px);
  }

  body {
    font-family: Inter, Roboto, "Helvetica Neue", "Arial Nova", "Nimbus Sans",
      Arial, sans-serif;
    font-size: 1rem;

    background-color: var(--stone-1);
    color: var(--stone-10);
    padding: var(--size-6);

    display: flex;
    flex-direction: column;
    gap: var(--size-6);
  }

  a {
    color: var(--stone-10);
    text-decoration: underline;
  }

  svg {
    width: 1.5em;
    height: 1.5em;
  }

  /* 
 * Outline color 
 */
  a,
  button {
    &:focus-visible {
      outline-color: var(--outline-color);
      outline-offset: var(--outline-offset);
    }
  }

  table {
    border-collapse: collapse;
  }
}

@layer utilities {
  .visually-hidden:not(:focus):not(:active) {
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    height: 1px;
    overflow: hidden;
    position: absolute;
    white-space: nowrap;
    width: 1px;
  }
}
View Compiled
document.documentElement.addEventListener("click", (e) => {
  if (e.target.dataset.viewType) {
    document.querySelector(".user-datalist-type").style.setProperty("--user-datalist-view", e.target.dataset.viewType);
  }
});

function formatFullName(user) {
  return `${user.name.first} ${user.name.last}`;
}

const getUserDatalistTemplate = (users) => `<div class="user-datalist-type">
  <table class="user-datalist">
    <thead>
      <tr>
        <th><span class="visually-hidden">Picture</span></th>
        <th>Name</th>
        <th>Email</th>
        <th>Date of birth</th>
        <th>Phone</th>
        <th><span class="visually-hidden">Actions</span></th>
      </tr>
    </thead>
    <tbody>
      ${users?.map((user) => {
        const fullName = formatFullName(user);
        const dateOfBirth = new Date(user.dob.date);
        const detailUrl = `/users/${user.login.uuid}`;
        return `<tr key="${user.login.uuid}" class="user-dataitem" data-href="${detailUrl}">
            <td class="user-dataitem__thumbnail">
              <img class="user-dataitem__image" src="${user.picture.thumbnail}" alt="Picture of ${fullName}" />
            </td>
            <td class="user-dataitem__full-name">${fullName}</td>
            <td class="user-dataitem__email">${user.email}</td>
            <td class="user-dataitem__date-of-birth">${dateOfBirth.toLocaleDateString()}</td>
            <td class="user-dataitem__phone">${user.phone}</td>
            <td class="user-dataitem__actions">
              <a class="user-dataitem__action--detail" href="${detailUrl}">
                <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="m12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8l-8-8z"/></svg>
                <span class="visually-hidden">More details about ${fullName}</span>
              </a>
            </td>
          </tr>`;
      }).join('')}
    </tbody>
  </table>
</div>`;

fetch("https://randomuser.me/api?results=10")
  .then((response) => response.json())
  .then(({ results }) => {
    const wrapper = document.querySelector(".user-datalist-wrapper");
    wrapper.innerHTML = getUserDatalistTemplate(results);
  });
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.