Replicating Hulu’s Categories Mosaic with CSS Grid

screenshot

The Hulu homepage contains a mosaic of show and movie categories. However, it’s a huge, single image (including the text), making the content inaccessible to screen readers and search engines. Learn how to replicate the layout two ways: once with Flexbox, once with Grid (this page).

There is often more than one way to achieve the same result in CSS. Such is the case with this layout. The HTML is the same for both this Grid version and the Flexbox version, as is the vast majority of the CSS. The only difference here is that the layout is set to a Grid of 6 x 3 (rows x columns) when the viewport is wide enough to fit them comfortably. Then it’s just a matter of telling each Grid item to span six, three, or two rows to occupy 100%, 50%, or 33.33%, respectively, of the Grid’s height. (Bonus tip!: there is yet a third way to do this with most of the same code: Multi-column Layout.)

We can use other layout techniques in combination with Grid as appropriate. For instance, I set position: relative on each of the six items to allow me to position the “Show Title” and “LOGO” text absolutely within each one. There are other ways you could place those bits of text near the bottom, including applying Flexbox to each item, but I think this approach is simpler to implement. And it demonstrates that not all of your hard-earned knowledge of older layout methods is made oblivious by Grid.

The Finished Example Layout

Images courtesy of Unsplash

The Code

HTML
  <ul class="Categories">
    <li class="Cat Cat--100">
      <a href="#" class="Cat__link">
        <p class="Cat__type">New on Hulu</p>
        <p class="Cat__title">Hulu with Live TV</p>
        <p class="Cat__show">Show Title</p>
        <p class="Cat__logo">logo</p>
      </a>
    </li>
    <li class="Cat Cat--50">
      <a href="#" class="Cat__link">
        <p class="Cat__type">Groundbreaking</p>
        <p class="Cat__title">Hulu Originals</p>
        <p class="Cat__show">Show Title</p>
        <p class="Cat__logo">logo</p>
      </a>
    </li>
    <li class="Cat Cat--50">
      <a href="#" class="Cat__link">
        <p class="Cat__type">New and Classic</p>
        <p class="Cat__title">Movies</p>
        <p class="Cat__show">Show Title</p>
        <p class="Cat__logo">logo</p>
      </a>
    </li>
    <li class="Cat Cat--33">
      <a href="#" class="Cat__link">
        <p class="Cat__type">Current</p>
        <p class="Cat__title">Seasons</p>
        <p class="Cat__show">Show Title</p>
        <p class="Cat__logo">logo</p>
      </a>
    </li>
    <li class="Cat Cat--33">
      <a href="#" class="Cat__link">
        <p class="Cat__type">Exclusive</p>
        <p class="Cat__title">Past Seasons</p>
        <p class="Cat__show">Show Title</p>
        <p class="Cat__logo">logo</p>
      </a>
    </li>
    <li class="Cat Cat--33">
      <a href="#" class="Cat__link">
        <p class="Cat__type">For All Ages</p>
        <p class="Cat__title">Kids</p>
        <p class="Cat__show">Show Title</p>
        <p class="Cat__logo">logo</p>
      </a>
    </li>
  </ul>

  <p class="Credit Credit--rt">Images courtesy of <a href="https://unsplash.com/">Unsplash</a></p>

CSS
html {
  box-sizing: border-box;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

body {
  margin: 0;
}

/* Container of whole unit
----------------------------- */
.Categories {
  list-style: none;
  margin: 0 auto;
  max-width: 1600px;
  padding-left: 0;
  width: 100%;
}

/* Apply Grid only when viewport at least this wide */
@media only screen and (min-width: 40em) {
  .Categories {
    height: calc(100vh - 80px);
    max-height: 800px;
    min-height: 400px;
    /* Define the grid */
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(6, 1fr);
    grid-auto-flow: column;
  }
}

/* Category (Grid items)
----------------------------- */
.Cat {
  background-color: #222;
  background-position: center center;
  background-size: cover;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}

/* :::: Item size types (by height) :::: */
/* Stacked vertically by default */
.Cat--100 {
  min-height: 55vh;
}

.Cat--50 {
  min-height: 30vh;
}

.Cat--33 {
  min-height: 20vh;
}

/* Three columns in these wider viewports */
@media only screen and (min-width: 40em) {
  /* Place items on the grid */
  .Cat--100 {
    /* 6 of the 6 rows = 100% of the grid's height  */
    grid-row: span 6;
  }
  .Cat--50 {
    /* 3 of the 6 rows = 50% of the grid's height  */
    grid-row-end: span 3;
  }
  .Cat--33 {
    /* 2 of the 6 rows = 33.33% of the grid's height  */
    grid-row-end: span 2;
  }
}

/* :::: Items content :::: */
/* Most of what follows is just about making them look nice. */
.Cat__link {
  color: #fff;
  display: block;
  padding: 12px 13px;
  position: relative;
  height: 100%;
  text-decoration: none;
}

@media only screen and (min-width: 40em) {
  .Cat__link {
    padding: 24px 26px;
  }
}

.Cat__type {
  font-size: .75em;
  letter-spacing: .1em;
  margin-bottom: 6px;
  margin-top: 0;
  text-transform: uppercase;
}

.Cat__title {
  font-size: 1.6em;
  margin-top: 0;
}

.Cat__show, .Cat__logo {
  position: absolute;
}

.Cat__show {
  bottom: 14px;
  font-size: .75em;
}

.Cat__logo {
  bottom: 4px;
  font-family: impact;
  font-size: 1.125em;
  letter-spacing: .07em;
  text-transform: uppercase;
  right: 26px;
}

/* Background imgs. In practice, replace nth-child selectors with classes per your preference. */
.Cat:nth-child(1) {
  background-image: url(../../../sites/hulu-grid/img/sky.jpg);
}

.Cat:nth-child(2) {
  background-image: url(../../../sites/hulu-grid/img/lake.jpg);
}

.Cat:nth-child(3) {
  background-image: url(../../../sites/hulu-grid/img/space-station.jpg);
}

.Cat:nth-child(4) {
  background-image: url(../../../sites/hulu-grid/img/forest.jpg);
}

.Cat:nth-child(5) {
  background-image: url(../../../sites/hulu-grid/img/wood-floor.jpg);
}

.Cat:nth-child(6) {
  background-image: url(../../../sites/hulu-grid/img/sparkler.jpg);
}