Feature

Performance

Fast by default

Spruce generates only what you use. Turn off the parts you don’t need with the generators, and ship a few kilobytes of CSS.

  • Opt-in utilities
  • Tree-shakable generators
  • No runtime JavaScript
Read about the generators

Theming

One markup, many looks

Switch between eight design styles and the dark mode with a single attribute, or create your own style with a few tokens.

  • Eight built-in styles
  • Dark mode that follows the system
  • Scoped styles for any element
Explore the styles
@use 'sprucecss/scss/spruce' as *;

.l-feature {
  &__inner {
    display: flex;
    flex-direction: column;
    gap: spacer-clamp('xl', 'xxl');

    @include breakpoint('md') {
      gap: 0;
    }
  }
}

.feature {
  $this: &;
  --column: 1;
  align-items: center;
  display: grid;
  grid-template-columns: repeat(var(--column), minmax(0, 1fr));
  row-gap: spacer('m');

  @include breakpoint('md') {
    --column: 2;
  }

  &--start {
    @include breakpoint('md') {
      #{$this}__caption {
        padding-inline-start: spacer-clamp('l', 'xl');
      }
    }
  }

  &--end {
    @include breakpoint('md') {
      #{$this}__thumbnail {
        order: 1;
      }

      #{$this}__caption {
        padding-inline-end: spacer-clamp('l', 'xl');
      }
    }
  }

  &__thumbnail {
    aspect-ratio: 4 / 3;
    block-size: 100%;
    border-radius: radius('lg');
    object-fit: cover;
    object-position: center;
  }

  &__caption {
    @include layout-stack('s');
    align-content: start;
    display: grid;

    @include breakpoint('md') {
      padding-block: spacer-clamp('xl', 8rem, 7.5vw);
    }
  }

  &__subtitle {
    align-items: center;
    background-color: color-alpha('primary', 0.06);
    border: config('border-width-base', $display) solid color-alpha('primary', 0.15);
    border-radius: radius('pill');
    color: color('primary');
    display: inline-flex;
    font-size: 0.9375rem;
    font-weight: 600;
    gap: 0.65rem;
    justify-self: start;
    padding: 0.15em spacer('s');
    text-align: start;
  }

  &__title {
    font-size: responsive-font-size(3rem, 25, '4vw + 1rem');
  }

  &__text {
    color: color('muted');
    font-size: config('font-size-lead', $typography);
  }

  &__list {
    list-style: none;
    padding: 0;
    @include layout-stack('xs');

    li {
      align-items: center;
      display: flex;
      gap: spacer('xs');

      // A check mark drawn with borders; it's decorative, the list items carry the meaning.
      /* stylelint-disable property-disallowed-list */
      &::before {
        block-size: 0.35em;
        border-block-end: 2px solid color('primary');
        border-left: 2px solid color('primary');
        content: '';
        flex-shrink: 0;
        inline-size: 0.7em;
        margin-block-end: 0.2em;
        rotate: -45deg;
      }
      /* stylelint-enable property-disallowed-list */
    }
  }

  &__btn {
    justify-self: start;
    margin-block-start: spacer('s');
  }

}

[dir='rtl'] .feature__btn .btn__icon {
  scale: -1 1;
}
<div class="l-feature">
    <div class="container">
        <div class="l-feature__inner">
            <article class="feature feature--start" aria-labelledby="f-1">
                <img class="feature__thumbnail" src="/img/blog/why-you-should-choose-eleventy-as-your-static-site-generator.png" alt="" loading="lazy" width="1200" height="630">
                <div class="feature__caption">
                    <p class="feature__subtitle">Performance</p>
                    <h2 class="feature__title" id="f-1">Fast by default</h2>
                    <p class="feature__text">Spruce generates only what you use. Turn off the parts you don’t need with the generators, and ship a few kilobytes of CSS.</p>
                    <ul class="feature__list">
                        <li>Opt-in utilities</li>
                        <li>Tree-shakable generators</li>
                        <li>No runtime JavaScript</li>
                    </ul>
                    <a class="btn btn--primary feature__btn" href="#">
                        Read about the generators
                        <svg class="btn__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
                    </a>
                </div>
            </article>
            <article class="feature feature--end" aria-labelledby="f-2">
                <img class="feature__thumbnail" src="/img/blog/the-different-types-of-css-frameworks.png" alt="" loading="lazy" width="1200" height="630">
                <div class="feature__caption">
                    <p class="feature__subtitle">Theming</p>
                    <h2 class="feature__title" id="f-2">One markup, many looks</h2>
                    <p class="feature__text">Switch between eight design styles and the dark mode with a single attribute, or create your own style with a few tokens.</p>
                    <ul class="feature__list">
                        <li>Eight built-in styles</li>
                        <li>Dark mode that follows the system</li>
                        <li>Scoped styles for any element</li>
                    </ul>
                    <a class="btn btn--primary feature__btn" href="#">
                        Explore the styles
                        <svg class="btn__icon" aria-hidden="true" fill="none" focusable="false" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
                    </a>
                </div>
            </article>
        </div>
    </div>
</div>

Feature

Alternating image and text rows to present product features: a label, a headline, a short description, a list of benefits and a call to action.

Variations

  • .feature--start: the image comes first (at the start side).
  • .feature--end: the image goes to the end side from the md breakpoint.

Alternate them to create a zigzag rhythm down the page.

Anatomy

  • .l-feature follows the block anatomy: .l-feature > .container > .l-feature__inner.
  • .feature is an article named by its title, with the image (__thumbnail) and the caption: __subtitle (a small label), __title, __text, __list (benefits with check marks) and __btn.

Accessibility

  • The image is decorative here (alt=""): the text carries the message. Give it an alt text when it shows something the text doesn't (like a screenshot with data).
  • On phones, the image always comes first in the visual order, and the DOM order (image, then text) stays the same, so the reading order matches what you see.
  • The check marks are pseudo-elements; the list is read as a plain list.

Technical Details

  • A one-column grid on phones and two columns from the md breakpoint; order moves the image to the end for --end. The arrow in the button flips in RTL.
  • The colors come from the tokens (the subtitle uses color-alpha('primary', …)), so the old feature color group isn't needed.
  • For a full-width version with a media slot (video, screenshot), see the Feature Media 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.