Lesson

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

.lesson-cards {
  @include clear-list;
  @include layout-grid('m', 18rem);

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

.lesson-card {
  @include a11y-card-link('.lesson-card__link', true);
  background-color: color('surface');
  block-size: 100%;
  border: config('border-width-base', $display) solid color('border');
  border-radius: radius('lg');
  box-shadow: shadow('sm');
  display: flex;
  flex-direction: column;
  overflow: hidden;

  &__media {
    aspect-ratio: 16 / 9;
    background-color: color('surface-muted');
    overflow: hidden;
    position: relative;

    img {
      block-size: 100%;
      inline-size: 100%;
      object-fit: cover;
    }

    // A shade, so the icon and the duration stay readable on any image.
    &::after {
      background-image: linear-gradient(to top, hsl(0deg 0% 0% / 45%), transparent 60%);
      content: '';
      inset: 0;
      position: absolute;
    }
  }

  &__duration {
    background-color: hsl(0deg 0% 0% / 70%);
    border-radius: radius('sm');
    color: hsl(0deg 0% 100%);
    font-size: config('font-size-sm', $typography);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    inset-block-end: spacer('xs');
    inset-inline-end: spacer('xs');
    line-height: 1.4;
    padding: 0 spacer('xxs');
    position: absolute;
  }

  &__icon {
    align-items: center;
    background-color: color('surface');
    block-size: 2.75rem;
    border-radius: 50%;
    box-shadow: shadow('md');
    color: color('primary');
    display: flex;
    inline-size: 2.75rem;
    inset: 0;
    justify-content: center;
    margin: auto;
    position: absolute;

    svg {
      block-size: 1.15rem;
      inline-size: 1.15rem;
    }
  }

  &__body {
    flex-grow: 1;
    padding: spacer('s') spacer('m') spacer('m');
    @include layout-stack('xxs'); // module, title, access
  }

  &__module {
    color: color('primary');
    font-size: config('font-size-sm', $typography);
    font-weight: 700;
  }

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

  &__link {
    color: inherit;
    text-decoration: none;
  }

  &__access {
    color: color('muted');
    font-size: config('font-size-sm', $typography);

    &--free {
      color: color('success', 'alert');
      font-weight: 600;
    }
  }

  @include motion-safe {
    img {
      transition: scale 0.3s ease;
    }

    &:hover img {
      scale: 1.04;
    }
  }
}
<ul class="lesson-cards" role="list">
    <li>
        <article class="lesson-card">
            <div class="lesson-card__media">
                <img src="https://images.unsplash.com/photo-1517694712202-14dd9538aa97?auto=format&amp;fit=crop&amp;w=640&amp;q=70" alt="" loading="lazy" width="640" height="360">
                <span class="lesson-card__duration"><span class="sr-only">Duration: </span>8:02</span>
                <span class="lesson-card__icon" aria-hidden="true">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false"><path d="M7 4v16l13-8z"/></svg>
                </span>
            </div>
            <div class="lesson-card__body">
                <p class="lesson-card__module">The cascade</p>
                <h3 class="lesson-card__title"><a class="lesson-card__link" href="#">How the cascade really works</a></h3>
                <p class="lesson-card__access lesson-card__access--free">Free preview</p>
            </div>
        </article>
    </li>
    <li>
        <article class="lesson-card">
            <div class="lesson-card__media">
                <img src="https://images.unsplash.com/photo-1498050108023-c5249f4df085?auto=format&amp;fit=crop&amp;w=640&amp;q=70" alt="" loading="lazy" width="640" height="360">
                <span class="lesson-card__duration"><span class="sr-only">Duration: </span>14:36</span>
                <span class="lesson-card__icon" aria-hidden="true">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><rect width="18" height="11" x="3" y="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
                </span>
            </div>
            <div class="lesson-card__body">
                <p class="lesson-card__module">Layout</p>
                <h3 class="lesson-card__title"><a class="lesson-card__link" href="#">Container queries in practice</a></h3>
                <p class="lesson-card__access">Included in the video course</p>
            </div>
        </article>
    </li>
    <li>
        <article class="lesson-card">
            <div class="lesson-card__media">
                <img src="https://images.unsplash.com/photo-1555066931-4365d14bab8c?auto=format&amp;fit=crop&amp;w=640&amp;q=70" alt="" loading="lazy" width="640" height="360">
                <span class="lesson-card__duration"><span class="sr-only">Duration: </span>11:20</span>
                <span class="lesson-card__icon" aria-hidden="true">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><rect width="18" height="11" x="3" y="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
                </span>
            </div>
            <div class="lesson-card__body">
                <p class="lesson-card__module">Design tokens</p>
                <h3 class="lesson-card__title"><a class="lesson-card__link" href="#">A token system that scales</a></h3>
                <p class="lesson-card__access">Included in the video course</p>
            </div>
        </article>
    </li>
</ul>

Lesson

On this page

A card for a video lesson: a 16:9 thumbnail with the duration, the module, the title and whether it's a free preview or part of the paid course.

Technical Details

  • The whole card is clickable with the a11y-card-link() mixin, but only the title is a link, so screen readers hear the lesson title once.
  • The play icon (free preview) and the lock icon (paid lesson) are decorative; the text under the title says the same ("Free preview", "Included in the video course"), so the meaning doesn't depend on the icon or the color.
  • The duration has a hidden "Duration:" label and tabular numbers; a shade at the bottom of the image keeps it readable on any thumbnail.
  • The cards sit in a layout-grid() list; override them with lessons ([{ title, module, duration, image, free }]).

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.