Category Grid

@use 'sprucecss/scss/spruce' as *;

.category-grid {
  @include layout-stack('m');

  &__header {
    align-items: baseline;
    display: flex;
    flex-wrap: wrap;
    gap: spacer('xs') spacer('s');
    justify-content: space-between;
  }

  &__title {
    font-size: font-size('h3');
  }

  &__all {
    font-weight: 600;
  }

  &__list {
    display: grid;
    gap: spacer('s');
    grid-auto-rows: 11rem;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    list-style: none;
    padding: 0;

    @include breakpoint('md') {
      gap: spacer('m');
      grid-auto-rows: 13rem;
      grid-template-columns: repeat(4, minmax(0, 1fr));
    }

    // The items sit side by side: clear the stack margins of the base list style.
    > li {
      margin-block: 0;
      padding-block: 0;
    }
  }

  &__item {
    &--featured {
      grid-column: span 2;

      @include breakpoint('md') {
        grid-row: span 2;
      }
    }
  }

  &__link {
    block-size: 100%;
    border-radius: radius('lg');
    color: hsl(0deg 0% 100%);
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    overflow: hidden;
    padding: spacer('m');
    position: relative;
    text-decoration: none;

    // A gradient keeps the white text readable on any photo.
    &::after {
      background-image: linear-gradient(to top, hsl(0deg 0% 0% / 70%), hsl(0deg 0% 0% / 5%) 60%);
      content: '';
      inset: 0;
      position: absolute;
    }

    img {
      block-size: 100%;
      inline-size: 100%;
      inset: 0;
      object-fit: cover;
      position: absolute;
      @include transition($property: 'scale');
    }

    &:hover img {
      @include motion-safe {
        scale: 1.05;
      }
    }

    &:hover .category-grid__name {
      text-decoration: underline;
    }

    &:focus-visible {
      outline: 2px solid color('focus-ring');
      outline-offset: 3px;
    }
  }

  &__name,
  &__count {
    position: relative;
    z-index: 1;
  }

  &__name {
    font-family: config('font-family-heading', $typography);
    font-size: font-size('h5');
    font-weight: config('font-weight-heading', $typography);
  }

  &__item--featured &__name {
    font-size: font-size('h3');
  }

  &__count {
    font-size: config('font-size-sm', $typography);
    opacity: 0.85;
  }
}
<section class="category-grid" aria-labelledby="category-grid-title">
    <header class="category-grid__header">
        <h2 class="category-grid__title" id="category-grid-title">Shop by category</h2>
        <a class="category-grid__all" href="#">All categories</a>
    </header>
    <ul class="category-grid__list" role="list">
        <li class="category-grid__item category-grid__item--featured">
            <a class="category-grid__link" href="#">
                <img src="https://images.unsplash.com/photo-1484101403633-562f891dc89a?auto=format&amp;fit=crop&amp;w=900&amp;q=70" alt="" loading="lazy">
                <span class="category-grid__name">Home &amp; Living</span>
                <span class="category-grid__count">248 products</span>
            </a>
        </li>
        <li class="category-grid__item">
            <a class="category-grid__link" href="#">
                <img src="https://images.unsplash.com/photo-1553062407-98eeb64c6a62?auto=format&amp;fit=crop&amp;w=500&amp;q=70" alt="" loading="lazy">
                <span class="category-grid__name">Bags</span>
                <span class="category-grid__count">64 products</span>
            </a>
        </li>
        <li class="category-grid__item">
            <a class="category-grid__link" href="#">
                <img src="https://images.unsplash.com/photo-1505740420928-5e560c06d30e?auto=format&amp;fit=crop&amp;w=500&amp;q=70" alt="" loading="lazy">
                <span class="category-grid__name">Audio</span>
                <span class="category-grid__count">37 products</span>
            </a>
        </li>
        <li class="category-grid__item">
            <a class="category-grid__link" href="#">
                <img src="https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?auto=format&amp;fit=crop&amp;w=500&amp;q=70" alt="" loading="lazy">
                <span class="category-grid__name">Coffee</span>
                <span class="category-grid__count">52 products</span>
            </a>
        </li>
        <li class="category-grid__item">
            <a class="category-grid__link" href="#">
                <img src="https://images.unsplash.com/photo-1523275335684-37898b6baf30?auto=format&amp;fit=crop&amp;w=500&amp;q=70" alt="" loading="lazy">
                <span class="category-grid__name">Watches</span>
                <span class="category-grid__count">29 products</span>
            </a>
        </li>
    </ul>
</section>

Category Grid

"Shop by category" tiles with a photo, the category name and the number of products, with the first category featured as a larger tile.

Anatomy

  • .category-grid__header: the title and a link to all categories.
  • .category-grid__list: a grid of __items; each is one __link with the image, the __name and the __count. The first item has the --featured modifier.

Accessibility and Usability

  • Every tile is one link, and its text ("Bags, 64 products") is its accessible name; the image is decorative (alt="").
  • The text sits on a dark gradient, so it stays readable on any photo; the focus ring is offset from the rounded tile.
  • The zoom on hover only runs when the user doesn't prefer reduced motion.

Technical Details

  • Two columns on small screens (the featured tile spans both), and four from the md breakpoint, where the featured tile spans two columns and two rows. With five categories, the grid is full on both layouts.
  • The rows have a fixed height (grid-auto-rows), and the images cover the tile with object-fit, so photos of any size line up.

Documentation

Learn about Spruce CSS through our extensive documentation.

Components

Explore our UI library of accessible components built with Spruce CSS.

Blocks and Pages

Full-width page blocks and complete page templates, ready to copy.

Find us on GitHub

Did you find a bug? Have an idea or a question? Please open an issue to help us develop the project.