Search code examples
htmlcssalignment

Align items in list of list


I have some HTML structure like this:

<ul>
<li>
  <h2>Item 1</h2>
  <dl>
    <div><dt>attr1</dt><dd>value a</dd></div>
    <div><dt>attr2</dt><dd>value b</dd></div>
    <div><dt>attr3</dt><dd>value c</dd></div>
  </dl>
</li>
<li>
  <h2>Item Another One Here</h2>
  <dl>
    <div><dt>attr1</dt><dd>value x</dd></div>
    <div><dt>attr2</dt><dd>value some bit long</dd></div>
    <div><dt>attr3</dt><dd>value z</dd></div>
  </dl>
</li>
</ul>

I want it be rendered as:

---------------------------------------------------------------------
  Item 1
  attr 1: value a  attr 2: value b              attr 3: value c
---------------------------------------------------------------------
  Item Another One Here
  attr 1: value x  attr 2: value some bit long  attr 3: value z
---------------------------------------------------------------------

Attributes are aligned to each other based on the length of theirs contents.

How can I do that?

I would avoid change my markup to <table>, since they are not really tables. I had tried layout them using display: table-*, but that won't work for the title (which should take place of whole row instead of one cell).


Solution

  • This layout is possible with the CSS Grid Layout Module

    .container {
      display: inline-grid;
      grid-column-gap: 1em;
      grid-template-columns: repeat(3,auto);
      padding: 0;
      background-color: white;
      padding-bottom: 0.5em;
      border-bottom: 2px dashed;
    }
    
    .container li, .container dl {
      display: contents;
    }
    
    h2 {
      grid-column: 1 / -1;
      font-size: inherit;
      padding: 0.5em 0 0 0;
      border-top: 2px dashed;
    }
    
    
    dl > div, dd, dt {
      display: inline-block;
      padding: 0;
      margin: 0;
    }
    dt:after {
      content: ':'
    }
    dd {
     margin-left: 0.5em;
    }
    dl > div:nth-child(1) {
      background-color: lightblue;
    }
    dl > div:nth-child(2) {
      background-color: lightgreen;
    }
    dl > div:nth-child(3) {
      background-color: pink;
    }
    <ul class="container">
      <li>
        <h2>Item 1</h2>
        <dl>
          <div><dt>attr1</dt>
            <dd>value a</dd>
          </div>
          <div><dt>attr2</dt>
            <dd>value b</dd>
          </div>
          <div><dt>attr3</dt>
            <dd>value c</dd>
          </div>
        </dl>
      </li>
      <li>
        <h2>Item 2 - Another One Here</h2>
        <dl>
          <div><dt>attr1</dt>
            <dd>value y -bla bla </dd>
          </div>
          <div><dt>attr2</dt>
            <dd>value some bit long</dd>
          </div>
          <div><dt>attr3</dt>
            <dd>value z</dd>
          </div>
        </dl>
      </li>
      <li>
        <h2>Item 3 - Another One Here</h2>
        <dl>
          <div><dt>attr1</dt>
            <dd>value x</dd>
          </div>
          <div><dt>attr2</dt>
            <dd>value some much longer</dd>
          </div>
          <div><dt>attr3</dt>
            <dd>value z - blabl bla</dd>
          </div>
        </dl>
      </li>
    </ul>

    (Codepen demo)

    The difficulty here is since the li and dl elements aren't direct children of the grid container - you can't apply grid properties to it.

    Grid Layout Module Level 2 - Subgrids (currently a draft spec) are supposed to solve this problem, but in the meantime we can use display:contents to overcome this.

    See this post for more details.


    If your markup was flat to begin with, then you wouldn't need display: contents and CSS grid module could do this fine:

    Cross-browser demo (With 'flat' markup')

    .container {
      display: inline-grid;
      grid-column-gap: 1em;
      grid-template-columns: repeat(3,auto);
      padding: 0;
      background-color: white;
      padding-bottom: 0.5em;
      border-bottom: 2px dashed;
    }
    
    h2 {
      grid-column: 1 / -1;
      font-size: inherit;
      padding: 0.5em 0 0 0;
      border-top: 2px dashed;
    }
    
    dl, dt, dd {
      display: inline-block;
      padding: 0;
      margin: 0;
    }
    dt:after {
      content: ':'
    }
    dd {
     margin-left: 0.5em;
    }
    dl:nth-of-type(3n + 1) {
      background-color: lightblue;
    }
    dl:nth-of-type(3n + 2) {
      background-color: lightgreen;
    }
    dl:nth-of-type(3n) {
      background-color: pink;
    }
    <div class="container">
      <h2>Item 1</h2>
        <dl><dt>attr1</dt><dd>value a</dd></dl>
        <dl><dt>attr2</dt><dd>value b</dd></dl>
        <dl><dt>attr3</dt><dd>value c</dd></dl>
      <h2>Item 2 - Another One Here</h2>
        <dl><dt>attr1</dt><dd>value x</dd></dl>
        <dl><dt>attr2</dt><dd>value some bit long</dd></dl>
        <dl><dt>attr3</dt><dd>value z</dd></dl>
      <h2>Item 3 - Another One Here</h2>
        <dl><dt>attr1</dt><dd>value x</dd></dl>
        <dl><dt>attr2</dt><dd>value some much much much longer</dd></dl>
        <dl><dt>attr3</dt><dd>value z</dd></dl>
    </div>

    NB:

    In order to 'shrink-wrap' the grid I applied display: inline-grid to the grid container.