Search code examples
htmlcssgrid-layoutcss-grid

CSS Grid 4 column layout with 2 column titles for <dl>


Given a markup:

dl
  dt
  dd
  dd
  ..

  dt
  dd
  dd
  ...

I'm trying to achieve the following layout with CSS grid:

  dt      dt
dd  dd  dd  dd
dd  dd  dd  dd
dd  dd  dd  dd
dd  dd  dd  dd

My current approach is:

.time-table {
    display: grid;
    grid-gap: 1em;
    grid-template-columns: repeat(4, 1fr);
    grid-template-areas:
      "destination1    destination1    destination2    destination2"
      "time             time             time             time";
    list-style: none;
    padding: 0;
  }

  .time-table__time {
    background-color: #34ace0;
    display: block;
    color: #fff;
    margin: 0;
    grid-area: time;
  }

  .time-table__destination::before {
    content: '\021D2';
    display: inline-block;
    transform: translateX(-5px);
  }

  .time-table__destination1 {
    grid-area: destination1;
  }

  .time-table__destination2 {
    grid-area: destination2;
  }

  @media (max-width: 35em) {
    .time-table {
      grid-template-columns: repeat(2, 1fr);
      grid-template-areas:
        "destination1  destination2"
        "time          time";
    }
    .time-table__destination::before {
      transform: translateX(-5px);
    }
  }
<dl class="time-table">
  <dt class="time-table__destination time-table__destination1">Metsakooli</dt>
  <dd class="time-table__time">23:22</dd>
  <dd class="time-table__time">23:32</dd>
  <dd class="time-table__time">23:42</dd>
  <dd class="time-table__time">23:52</dd>
  <dd class="time-table__time">00:02</dd>

  <dt class="time-table__destination time-table__destination2">Männiku</dt>
  <dd class="time-table__time">23:27</dd>
  <dd class="time-table__time">23:37</dd>
  <dd class="time-table__time">23:47</dd>
  <dd class="time-table__time">23:57</dd>
  <dd class="time-table__time">00:07</dd>
</dl>

Or without using grid's template areas:

.time-table {
    display: grid;
    grid-gap: 1em;
    grid-template-columns: repeat(4, 1fr);
    list-style: none;
    padding: 0;
  }

  .time-table__time {
    background-color: #34ace0;
    display: block;
    color: #fff;
    margin: 0;
  }

  .time-table__destination::before {
    content: '\021D2';
    display: inline-block;
    transform: translateX(-5px);
  }

  @media (max-width: 35em) {
    .time-table {
      grid-template-columns: repeat(2, 1fr);
      grid-template-areas:
        "destination1  destination2"
        "time          time";
    }
    .time-table__destination::before {
      transform: translateX(-5px);
    }
  }
<dl class="time-table">
  <dt class="time-table__destination time-table__destination1">Metsakooli</dt>
  <dd class="time-table__time">23:22</dd>
  <dd class="time-table__time">23:32</dd>
  <dd class="time-table__time">23:42</dd>
  <dd class="time-table__time">23:52</dd>
  <dd class="time-table__time">00:02</dd>

  <dt class="time-table__destination time-table__destination2">Männiku</dt>
  <dd class="time-table__time">23:27</dd>
  <dd class="time-table__time">23:37</dd>
  <dd class="time-table__time">23:47</dd>
  <dd class="time-table__time">23:57</dd>
  <dd class="time-table__time">00:07</dd>
</dl>

But as you can see, the first approach fails to render the different times under the appropriate route title. The second approach creates the rows, but doesn't list them in the correct order and fails to hoist the second title (). My questions are: - How can I make the times (<dd>) appear under their respective destination/route? - Is there a more elegant way to have the first row's cells span 2 columns and have the rest of the elements take the place of grid area time, like using grid-auto-rows? (I bet there is...)


Solution

  • The problem with the HTML that you've provided is that there's no way of associating the <dd> elements with a unique <dt> element, under which the .time-table__time elements should be aligned.

    It is possible, with JavaScript, to take advantage of the order property to provide that visual identity, but it's an overwrought solution to a problem that can be better solved by changing the HTML.

    The problem with using the grid-template-areas is, as you've found, that you create a named area that spans four columns; and all the <dd> elements are placed in that one named area.

    The revised HTML, below, uses two <dl> elements, each with its own <dt> and <dd> descendants to uniquely associate the two together; those <dl> elements are themselves wrapped in <li> elements, themselves the children of an <ol> element. This is because the HTML you're showing – without context – seems to be an ordered list of destinations.

    I was tempted to change the <dl> elements themselves into <ol> elements, given that they seem to be ordered lists of times, albeit with a title. However, semantics aside, the following is my suggestion of how you can produce your apparent requirements:

    /* Setting the margin and padding of all
       elements to zero: */
    
    *,
    ::before,
    ::after {
      margin: 0;
      padding: 0;
    }
    
    
    /* Adjust to taste: */
    
    html,
    body {
      background-color: #fff;
    }
    
    
    /* Defining a custom CSS Property for
       later use: */
    
    :root {
      --numParentColumns: 2;
    }
    
    
    /* Removing default list-styling: */
    
    ol,
    li,
    dl,
    dt,
    dd {
      list-style-type: none;
    }
    
    ol {
      /* Setting the display type of the <ol> element
         to that of 'grid': */
      display: grid;
      /* Defining the grid columns, using the previously-
         defined --numParentColumns variable to supply the
         integer to the repeat() function, and setting each
         column to the width of 1fr: */
      grid-template-columns: repeat(var(--numParentColumns), 1fr);
      /* Adjust to taste: */
      width: 90vw;
      grid-column-gap: 0.5em;
      margin: 0 auto;
    }
    
    ol>li dl {
      /* Setting the <dl> elements' display to 'grid': */
      display: grid;
      /* Defining the number of grid columns to that value
         held in the --numParentColumns variable, retaining
         the 1fr width: */
      grid-template-columns: repeat(var(--numParentColumns), 1fr);
      grid-column-gap: 0.25em;
    }
    
    dt.time-table__destination {
      /* Rather than using specific named grid-areas
         here we place the <dt> elements in column 1
         and span 2 columns: */
      grid-column: 1 / span 2;
      background-color: limegreen;
    }
    
    dt.time-table__destination::before {
      content: '\021D2';
      margin: 0 1em 0 0;
    }
    
    dd.time-table__time {
      background-color: #34ace0;
    }
    
    
    /* in displays where the width is at, or below,
       35em we update some properties: */
    
    @media (max-width: 35em) {
      dd.time-table__time {
        /* Here we place the <dd> elements in the
           first column and have them span 2
           columns: */
        grid-column: 1 / span 2;
      }
    }
    
    
    /* At page-widths less than 15em (not mentioned in
       the question): */
    
    @media (max-width: 15em) {
      /* We update the --numParentColumns variable to
         1, as a tangential demonstration of how useful
         CSS custom properties can be: */
       :root {
        --numParentColumns: 1;
      }
    }
    <ol>
      <li>
        <dl>
          <dt class="time-table__destination time-table__destination1">Metsakooli</dt>
          <dd class="time-table__time">23:22</dd>
          <dd class="time-table__time">23:32</dd>
          <dd class="time-table__time">23:42</dd>
          <dd class="time-table__time">23:52</dd>
          <dd class="time-table__time">00:02</dd>
        </dl>
      </li>
      <li>
        <dl class="time-table">
          <dt class="time-table__destination time-table__destination2">Männiku</dt>
          <dd class="time-table__time">23:27</dd>
          <dd class="time-table__time">23:37</dd>
          <dd class="time-table__time">23:47</dd>
          <dd class="time-table__time">23:57</dd>
          <dd class="time-table__time">00:07</dd>
        </dl>
      </li>
    </ol>

    References: