Gallery

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

.gallery {
  $this: &;
  @include clear-list;

  + .gallery {
    margin-block-start: spacer-clamp('l', 'xl');
  }

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

  &-item__link {
    block-size: 100%;
    border-radius: radius('md');
    display: block;
    overflow: hidden;

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

  @include motion-safe {
    &-item__link:hover img,
    &-item__link:focus-visible img {
      scale: 1.04;
    }
  }

  &--even {
    @include layout-grid('m', 15rem);

    img {
      aspect-ratio: 1;
    }

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

  &--uneven {
    display: grid;
    grid-auto-rows: min-content;
    grid-gap: spacer-clamp('s', 'm');
    grid-template-columns: repeat(6, minmax(0, 1fr));

    #{$this}-item--1 {
      grid-column: 1 / span 3;
      grid-row: 1 / span 1;
    }

    #{$this}-item--2 {
      grid-column: 4 / span 3;
      grid-row: 1 / span 1;
    }

    #{$this}-item--3 {
      grid-column: 1 / span 2;
      grid-row: 2 / span 1;
    }

    #{$this}-item--4 {
      grid-column: 3 / span 2;
      grid-row: 2 / span 1;
    }

    #{$this}-item--5 {
      grid-column: 5 / span 2;
      grid-row: 2 / span 1;
    }

    #{$this}-item--6 {
      grid-column: 1 / span 3;
      grid-row: 3 / span 1;

      img {
        aspect-ratio: 3 / 1.5;
      }
    }

    #{$this}-item--7 {
      grid-column: 1 / span 3;
      grid-row: 4 / span 1;

      img {
        aspect-ratio: 3 / 1.5;
      }
    }

    #{$this}-item--8 {
      grid-column: 4 / span 3;
      grid-row: 3 / span 2;

      img {
        aspect-ratio: auto;
      }
    }

    img {
      aspect-ratio: 3 / 2;
    }

    // On phones, the uneven layout falls back to two columns.
    @include breakpoint-down('sm') {
      grid-template-columns: repeat(2, minmax(0, 1fr));

      #{$this}-item {
        grid-column: auto;
        grid-row: auto;
      }
    }

    // The items sit side by side: clear the stack margins of the base list style.
    > li {
      margin-block: 0;
      padding-block: 0;
    }
  }
}
<ul class="gallery gallery--even" aria-label="Article covers">
    <li class="gallery-item">
        <a class="gallery-item__link" href="/img/blog/4-areas-where-modern-css-solves-a-lot.png">
            <img src="/img/blog/4-areas-where-modern-css-solves-a-lot.png" alt="Cover image: 4 areas where modern CSS solves a lot" loading="lazy" width="1200" height="630">
        </a>
    </li>
    <li class="gallery-item">
        <a class="gallery-item__link" href="/img/blog/8-helpful-sass-mixins-and-functions.png">
            <img src="/img/blog/8-helpful-sass-mixins-and-functions.png" alt="Cover image: 8 helpful Sass mixins and functions" loading="lazy" width="1200" height="630">
        </a>
    </li>
    <li class="gallery-item">
        <a class="gallery-item__link" href="/img/blog/about-modern-sass.png">
            <img src="/img/blog/about-modern-sass.png" alt="Cover image: About modern Sass" loading="lazy" width="1200" height="630">
        </a>
    </li>
    <li class="gallery-item">
        <a class="gallery-item__link" href="/img/blog/cracking-the-theme-switcher-and-dark-mode.png">
            <img src="/img/blog/cracking-the-theme-switcher-and-dark-mode.png" alt="Cover image: Cracking the theme switcher and dark mode" loading="lazy" width="1200" height="630">
        </a>
    </li>
    <li class="gallery-item">
        <a class="gallery-item__link" href="/img/blog/how-to-choose-a-css-framework.png">
            <img src="/img/blog/how-to-choose-a-css-framework.png" alt="Cover image: How to choose a CSS framework" loading="lazy" width="1200" height="630">
        </a>
    </li>
    <li class="gallery-item">
        <a class="gallery-item__link" href="/img/blog/introduction-of-spruce-ui.png">
            <img src="/img/blog/introduction-of-spruce-ui.png" alt="Cover image: Introduction of Spruce UI" loading="lazy" width="1200" height="630">
        </a>
    </li>
    <li class="gallery-item">
        <a class="gallery-item__link" href="/img/blog/the-simplest-sass-compile-setup.png">
            <img src="/img/blog/the-simplest-sass-compile-setup.png" alt="Cover image: The simplest Sass compile setup" loading="lazy" width="1200" height="630">
        </a>
    </li>
    <li class="gallery-item">
        <a class="gallery-item__link" href="/img/blog/writing-better-css.png">
            <img src="/img/blog/writing-better-css.png" alt="Cover image: Writing better CSS" loading="lazy" width="1200" height="630">
        </a>
    </li>
    <li class="gallery-item">
        <a class="gallery-item__link" href="/img/blog/why-use-sass.png">
            <img src="/img/blog/why-use-sass.png" alt="Cover image: Why use Sass" loading="lazy" width="1200" height="630">
        </a>
    </li>
</ul>
<ul class="gallery gallery--uneven" aria-label="Featured covers">
    <li class="gallery-item gallery-item--1">
        <a class="gallery-item__link" href="/img/blog/4-areas-where-modern-css-solves-a-lot.png">
            <img src="/img/blog/4-areas-where-modern-css-solves-a-lot.png" alt="Cover image: 4 areas where modern CSS solves a lot" loading="lazy" width="1200" height="630">
        </a>
    </li>
    <li class="gallery-item gallery-item--2">
        <a class="gallery-item__link" href="/img/blog/8-helpful-sass-mixins-and-functions.png">
            <img src="/img/blog/8-helpful-sass-mixins-and-functions.png" alt="Cover image: 8 helpful Sass mixins and functions" loading="lazy" width="1200" height="630">
        </a>
    </li>
    <li class="gallery-item gallery-item--3">
        <a class="gallery-item__link" href="/img/blog/about-modern-sass.png">
            <img src="/img/blog/about-modern-sass.png" alt="Cover image: About modern Sass" loading="lazy" width="1200" height="630">
        </a>
    </li>
    <li class="gallery-item gallery-item--4">
        <a class="gallery-item__link" href="/img/blog/cracking-the-theme-switcher-and-dark-mode.png">
            <img src="/img/blog/cracking-the-theme-switcher-and-dark-mode.png" alt="Cover image: Cracking the theme switcher and dark mode" loading="lazy" width="1200" height="630">
        </a>
    </li>
    <li class="gallery-item gallery-item--5">
        <a class="gallery-item__link" href="/img/blog/how-to-choose-a-css-framework.png">
            <img src="/img/blog/how-to-choose-a-css-framework.png" alt="Cover image: How to choose a CSS framework" loading="lazy" width="1200" height="630">
        </a>
    </li>
    <li class="gallery-item gallery-item--6">
        <a class="gallery-item__link" href="/img/blog/introduction-of-spruce-ui.png">
            <img src="/img/blog/introduction-of-spruce-ui.png" alt="Cover image: Introduction of Spruce UI" loading="lazy" width="1200" height="630">
        </a>
    </li>
    <li class="gallery-item gallery-item--7">
        <a class="gallery-item__link" href="/img/blog/the-simplest-sass-compile-setup.png">
            <img src="/img/blog/the-simplest-sass-compile-setup.png" alt="Cover image: The simplest Sass compile setup" loading="lazy" width="1200" height="630">
        </a>
    </li>
    <li class="gallery-item gallery-item--8">
        <a class="gallery-item__link" href="/img/blog/writing-better-css.png">
            <img src="/img/blog/writing-better-css.png" alt="Cover image: Writing better CSS" loading="lazy" width="1200" height="630">
        </a>
    </li>
</ul>

Gallery

Two image grid layouts: an even, intrinsic grid that fits as many columns as there is room for, and an uneven, editorial grid with mixed sizes.

Variations

  • .gallery--even: square tiles with the layout-grid mixin (min. 15rem per column), no breakpoints needed.
  • .gallery--uneven: a six-column grid with eight placed items (.gallery-item--1 … --8). Below the sm breakpoint it falls back to two even columns.

Anatomy

  • .gallery is a ul with an aria-label, so it's announced as a list of images with a count.
  • .gallery-item is an li; .gallery-item__link links to the full-size image.

Accessibility

  • Each image is the only content of its link, so its alt text is the link's name. An empty alt here would leave screen reader users with unnamed links (earlier versions had this problem).
  • Write what's in the image in the alt, not "image of".
  • The zoom on hover and focus is turned off for reduced motion; keyboard focus gets an outline.

Technical Details

  • The images use object-fit: cover and aspect-ratio, so different image sizes line up.
  • Set width, height and loading="lazy" on the images to avoid layout shifts and save bandwidth.
  • For a lightbox that opens the images in a dialog, use the Gallery block.

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.