Info Card

  • Custom web development

    Websites tailored to your brand: responsive, accessible and fast, with an editor your team will enjoy.

    Learn more
  • E-commerce solutions

    Secure, scalable online stores that make checkout easy and turn visitors into customers.

    Learn more
  • Maintenance and support

    Updates, backups, security monitoring and a real person to talk to when something breaks.

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

.info-card-list {
  @include clear-list;
  display: grid;
  gap: spacer('m');
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  inline-size: 100%;

  > li {
    margin-block: 0;
  }
}

.info-card {
  $this: &;
  @include a11y-card-link('.info-card__link', true);
  @include transition($property: 'background-color, border-color, color, translate');
  background-color: color('surface');
  border: config('border-width-base', $display) solid color('border');
  border-radius: radius('lg');
  color: color('text');
  display: flex;
  flex-direction: column;
  gap: spacer('m');
  padding: spacer-clamp('m', 'l');

  // A high-contrast inverted state on hover and keyboard focus.
  &:hover,
  &:focus-within {
    background-color: color('primary');
    border-color: color('primary');
    color: color('primary-foreground', 'btn');

    #{$this}__title,
    #{$this}__link,
    #{$this}__icon {
      color: inherit;
    }

    #{$this}__icon {
      background-color: hsl(0deg 0% 100% / 15%);
    }
  }

  @include motion-safe {
    &:hover {
      translate: 0 -0.5rem;
    }
  }

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

  &__icon {
    @include transition($property: 'background-color, color');
    align-items: center;
    background-color: color-alpha('primary', 0.1);
    block-size: 3rem;
    border-radius: radius('md');
    color: color('primary');
    display: flex;
    inline-size: 3rem;
    justify-content: center;

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

  &__caption {
    @include layout-stack('xs');
  }

  &__title {
    font-size: font-size('h4');
    margin-block: 0;
  }

  &__link {
    font-weight: 700;
    margin-block-start: auto;

    &:focus {
      outline: none;
    }
  }
}
<ul class="info-card-list">
    <li class="info-card">
        <span class="info-card__icon" aria-hidden="true">
            <svg fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m16 18 6-6-6-6M8 6l-6 6 6 6"/></svg>
        </span>
        <div class="info-card__caption">
            <h2 class="info-card__title" id="service-1">Custom web development</h2>
            <p class="info-card__text">Websites tailored to your brand: responsive, accessible and fast, with an editor your team will enjoy.</p>
        </div>
        <a class="info-card__link" href="#" aria-describedby="service-1">Learn more</a>
    </li>
    <li class="info-card">
        <span class="info-card__icon" aria-hidden="true">
            <svg fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="8" cy="21" r="1"/><circle cx="19" cy="21" r="1"/><path d="M2 2h3l2.7 13.4a2 2 0 0 0 2 1.6h9.7a2 2 0 0 0 2-1.6L23 6H6"/></svg>
        </span>
        <div class="info-card__caption">
            <h2 class="info-card__title" id="service-2">E-commerce solutions</h2>
            <p class="info-card__text">Secure, scalable online stores that make checkout easy and turn visitors into customers.</p>
        </div>
        <a class="info-card__link" href="#" aria-describedby="service-2">Learn more</a>
    </li>
    <li class="info-card">
        <span class="info-card__icon" aria-hidden="true">
            <svg fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
        </span>
        <div class="info-card__caption">
            <h2 class="info-card__title" id="service-3">Maintenance and support</h2>
            <p class="info-card__text">Updates, backups, security monitoring and a real person to talk to when something breaks.</p>
        </div>
        <a class="info-card__link" href="#" aria-describedby="service-3">Learn more</a>
    </li>
</ul>

Info Card

A service or feature card with an icon, a title, a short description and a link. The whole card is clickable and turns into a high-contrast, inverted state on hover and keyboard focus.

Anatomy

  • .info-card-list is a responsive list of cards (auto-fit columns, min. 16rem).
  • .info-card is one card with an icon (__icon), the caption (__title, __text) and a link (__link).

Accessibility

  • The a11y-card-link mixin stretches the link over the card, so the whole card is a target, but there is only one link in the tab order (not one per title, icon and button).
  • "Learn more" alone is a vague name, so the link is described by the card title (aria-describedby).
  • The inverted state also appears on :focus-within, with a focus outline around the card, so keyboard users see which card they're on.
  • The lift on hover only happens without reduced motion.

Technical Details

  • On hover, the card switches to the primary color and the button foreground color, so the contrast stays right in every style and the dark mode.
  • Don't put other interactive elements in the card: the stretched link sits above them.

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.