Product Cards

Demo

  • Lorem ipsum dolor sit amet, consectetur adipisicing elit. Perferendis repellat, similique aperiam nemo molestiae dolore suscipit nobis facere eaque praesentium minima repellendus dolor sunt expedita optio rem tempora dolorum porro?

  • Lorem ipsum dolor sit amet, consectetur adipisicing elit. Perferendis repellat, similique aperiam nemo molestiae dolore suscipit nobis facere eaque praesentium minima repellendus dolor sunt expedita optio rem tempora dolorum porro?

  • Lorem ipsum dolor sit amet, consectetur adipisicing elit. Perferendis repellat, similique aperiam nemo molestiae dolore suscipit nobis facere eaque praesentium minima repellendus dolor sunt expedita optio rem tempora dolorum porro?

  • Lorem ipsum dolor sit amet, consectetur adipisicing elit. Perferendis repellat, similique aperiam nemo molestiae dolore suscipit nobis facere eaque praesentium minima repellendus dolor sunt expedita optio rem tempora dolorum porro?

  • Lorem ipsum dolor sit amet, consectetur adipisicing elit. Perferendis repellat, similique aperiam nemo molestiae dolore suscipit nobis facere eaque praesentium minima repellendus dolor sunt expedita optio rem tempora dolorum porro?

  • Lorem ipsum dolor sit amet, consectetur adipisicing elit. Perferendis repellat, similique aperiam nemo molestiae dolore suscipit nobis facere eaque praesentium minima repellendus dolor sunt expedita optio rem tempora dolorum porro?

				
					
<main>
  <h1>Product Card Demo</h1>
  <ul class="product-card-list">
    <!-- Card Component -->
    <li class="product-card">
      <div class="header-wrapper">
        <!-- img alt attribute is set to blank to hide 
             the img from assistive technology as it is
             decorative.
          -->
        <img decoding="async" src="./testimg01.png" alt="">
        <h2>
          <a class="card-link" href="/product1">
            Product 1 Name
          </a>  
        </h2>
      </div>
      <p class="description">
        Lorem ipsum dolor sit amet, consectetur adipisicing 
        elit. Perferendis repellat, similique aperiam nemo 
        molestiae dolore suscipit nobis facere eaque 
        praesentium minima repellendus dolor sunt expedita 
        optio rem tempora dolorum porro?
      </p>
    </li>
    <!-- Card Component -->
    <li class="product-card">
      <div class="header-wrapper">
        <img decoding="async" src="./testimg01.png" alt="">
        <h2>
          <a class="card-link" href="/product1">
            Product 2 Name
          </a>  
        </h2>
      </div>
      <p class="description">
        Lorem ipsum dolor sit amet, consectetur adipisicing 
        elit. Perferendis repellat, similique aperiam nemo 
        molestiae dolore suscipit nobis facere eaque 
        praesentium minima repellendus dolor sunt expedita 
        optio rem tempora dolorum porro?
      </p>
    </li>
    <!-- Card Component -->
    <li class="product-card">
      <div class="header-wrapper">
        <img decoding="async" src="./testimg01.png" alt="">
        <h2>
          <a class="card-link" href="/product1">
            Product 3 Name
          </a>  
        </h2>
      </div>
      <p class="description">
        Lorem ipsum dolor sit amet, consectetur adipisicing 
        elit. Perferendis repellat, similique aperiam nemo 
        molestiae dolore suscipit nobis facere eaque 
        praesentium minima repellendus dolor sunt expedita 
        optio rem tempora dolorum porro?
      </p>
    </li>
    <!-- Card Component -->
    <li class="product-card">
      <div class="header-wrapper">
        <img decoding="async" src="./testimg01.png" alt="">
        <h2>
          <a class="card-link" href="/product1">
            Product 4 Name
          </a>  
        </h2>
      </div>
      <p class="description">
        Lorem ipsum dolor sit amet, consectetur adipisicing 
        elit. Perferendis repellat, similique aperiam nemo 
        molestiae dolore suscipit nobis facere eaque 
        praesentium minima repellendus dolor sunt expedita 
        optio rem tempora dolorum porro?
      </p>
    </li>
    <!-- Card Component -->
    <li class="product-card">
      <div class="header-wrapper">
        <img decoding="async" src="./testimg01.png" alt="">
        <h2>
          <a class="card-link" href="/product1">
            Product 5 Name
          </a>  
        </h2>
      </div>
      <p class="description">
        Lorem ipsum dolor sit amet, consectetur adipisicing 
        elit. Perferendis repellat, similique aperiam nemo 
        molestiae dolore suscipit nobis facere eaque 
        praesentium minima repellendus dolor sunt expedita 
        optio rem tempora dolorum porro?
      </p>
    </li>
    <!-- Card Component -->
    <li class="product-card">
      <div class="header-wrapper">
        <img decoding="async" src="./testimg01.png" alt="">
        <h2>
          <a class="card-link" href="/product1">
            Product 6 Name
          </a>  
        </h2>
      </div>
      <p class="description">
        Lorem ipsum dolor sit amet, consectetur adipisicing 
        elit. Perferendis repellat, similique aperiam nemo 
        molestiae dolore suscipit nobis facere eaque 
        praesentium minima repellendus dolor sunt expedita 
        optio rem tempora dolorum porro?
      </p>
    </li>
    </ul>
</main>
				
			
				
					
/* Styling per our companies style guides */

h1, h2, h3, h4, h5, h6 {
    font-family: 'Oswald', sans-serif;
}

* {
    font-family: 'Barlow', sans-serif;
}
/* end company styling */

:root {
    --link-color: black;
    --cur-link-color: #0050c8;
    --bg-color: white;
    --border-radius: 0.5rem;
}

/* We remove the list-style (bullet points). 
 * Flex and flex-wrap allow the list of product-cards
 * to wrap to a new line as the viewport changes.
 */
.product-card-list {
    display: flex;
    flex-wrap: wrap;
    gap: 2rem;
    list-style: none;
    padding: 0;
    margin: 1rem;
}

/****** li (card) **********/
.product-card-list .product-card  {
    position: relative;
    border-radius: var(--border-radius);
    color: #222;
    overflow: hidden;
    max-width: 25rem;
    box-shadow: 0px 2px 3px -1px #222;
    transition: scale 70ms ease, box-shadow 70ms ease;
    scale: 1;
}

/* enlarge the card component and change box shadow when
 * focused or hovered.
 */
.product-card-list .product-card:is(:hover, :focus-within) {
    scale: 1.015;
    box-shadow: 0px 4px 7px -2px #222;
}

/* Important! This provides the focus indicator. Since we 
 * are removing the focus indicator from the original anchor
 * element, we need to provide our own focus indicator. The
 * :focus-within pseudo-selector will be active when the
 * browser determines that focus is currently on an element
 * that is a descendant of this element - aka when focus is 
 * within this element.
 */
.product-card-list .product-card:focus-within {
    outline: 0.2rem solid black;
    outline-offset: 0.2rem;
}

/* visual formatting */
.product-card-list .product-card p {
    margin: 0;
    padding: 0rem 1rem 1rem;
}

.product-card-list .product-card h2 {
    margin: 0;
    padding: 0.75rem 1rem;
}

.product-card-list .header-wrapper {
    padding: 0;
}

.product-card-list img {
    width: 100%;
}

.product-card-list .header-wrapper {
    display: flex;
    flex-direction: column;
}

/* Removes the default focus indicator for the link. 
 * We only remove the default focus indicator because we
 * are adding our own.
 */
.card-link:focus-within {
    outline: none;
}

/* Here we take the a.card-link and add a pseudo ::after 
 * element that covers the entirety of the card component.
 * This works because the li.product-card element has the
 * property 'position: relative'. This functionality will
 * break if any of the a.card-link element's ancestors (aside 
 * from li.product-card) has a position property other than
 * 'static'. An element with 'position: absolute' is removed 
 * from the normal document flow, and is positioned relative
 * to the closest positioned (has a position property other 
 * than 'static') ancestor. In this example, the 
 * li.product-card is the closest positioned ancestor. Since 
 * the li.product-card contains all components that comprise 
 * the product card, giving a descendant element the properties 
 * in the following CSS declaration will give that element the 
 * same size as the li.product-card.
 */
.product-card-list .card-link::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    right: 0;
    left: 0;
}

.product-card-list .header-wrapper a {
    text-decoration: none;
    color: var(--link-color);
}

.product-card-list .product-card:active {
    scale: 1;
}

/*************** prefers-reduced-motion *****************/
/********************************************************/
/*  Operating systems allow users to reduce animations 
 *  or turn them off entirely. In Windows, this setting is
 *  called "Animation Effects". Turning off "Animation
 *  Effects" will activate the prefers-reduced-motion CSS
 *  media query. If the user has set this setting, then we 
 *  should honor it and reduce or remove any animations in 
 *  our CSS.
**********************************************************/

@media (prefers-reduced-motion) {
    .product-card-list li.product-card {
        transition: 0ms !important;
    }
}

/********** end prefers-reduced-motion ************/
				
			

Why use a card?

Card or tile components are frequently employed in web design for their versatility and ability to improve the user experience. These components offer a structured and visually appealing way to present various types of content, such as articles, products, user profiles, or interactive elements. They provide a clear and consistent visual hierarchy, making it easier for users to scan and engage with content. Cards can encapsulate rich media, making them suitable for showcasing images, videos, and other multimedia elements. Additionally, they can be interactive, allowing users to click, hover, or tap for more information or to navigate to the full content. Cards also adapt well to responsive design, ensuring a seamless user experience on different devices. Their modular nature makes them easy to reuse and update, promoting efficiency in development. 

What are some accessibility concerns?

  • Keyboard Navigation:
    • Ensure that card or tile components are keyboard accessible. Users should be able to navigate through and interact with cards using the Tab key, and the currently focused card should be visually distinguishable.
  • Focus Management:
    • When a card is selected, manage focus appropriately within the card. Set focus to the most relevant or interactive element within the card, making it easier for users to explore the card’s content.
  • Semantic Markup:
    • Use semantic HTML elements (e.g., <article>, <section>) to provide meaning to card content. This helps screen readers understand the structure and purpose of the card and its contents.
  • Text Alternatives:
    • Include descriptive alt text for images and icons within the cards so that users with visual impairments can understand the content. Provide concise, informative, and context-specific descriptions.
  • Interactive Elements:
    • If the card contains interactive elements like buttons or links, ensure that these elements are keyboard accessible and have clear, meaningful labels.
  • Color and Contrast:
    • Be mindful of color choices and contrast within cards. Ensure that text and interactive elements have sufficient contrast against their background for users with low vision or color blindness.
  • Content Order:
    • Maintain a logical content order within cards, considering how the content flows for screen reader users who may rely on a linear reading experience.
  • Alt Text for Card Groupings:
    • If you have a group of cards, provide an alternative text description for the entire group if it conveys a specific meaning or relationship (e.g., a set of related products).
Tested using
Chrome
with NVDA
Firefox
with NVDA
  • W3C SVG
    1.1.1
    Non-text Content ( level A ) :
    All non-text content that is presented to the user has a text alternative that serves the equivalent purpose.
  • W3C SVG
    1.3.2
    Meaningful Sequence ( level A ) :
    When the sequence in which content is presented affects its meaning, a correct reading sequence can be programmatically determined.
  • W3C SVG
    2.1.1
    Keyboard ( level A ) :
    All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes, except where the underlying function requires input that depends on the path of the user's movement and not just the endpoints.
  • W3C SVG
    2.4.3
    Focus Order ( level A ) :
    If a Web page can be navigated sequentially and the navigation sequences affect meaning or operation, focusable components receive focus in an order that preserves meaning and operability.
  • W3C SVG
    2.4.4
    Link Purpose (In Context) ( level A ) :
    The purpose of each link can be determined from the link text alone or from the link text together with its programmatically determined link context, except where the purpose of the link would be ambiguous to users in general.