Skip to main content

Card v1.5.0

Group content such as images, text, and related links, offering a compact and organized way to display information.

  • Tristique senectus et netus

    Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque.

  • Tristique et netus

    Habitant morbi tristique senectus et netus. Unde omnis iste natus error sit voluptatem accusantium doloremque.

  • Fames ac turpis egestas

    Pellentesque habitant tristique senectus et malesuada fames ac egestas. Sed ut perspiciatis unde omnis iste error sit voluptatem doloremque.

<ul class="card" role="list">

    <li class="card__item">

        <div class="card__media">

            <img alt="" src="https://radancy.dev/placeholder-450x225.png">

        </div>

        <h2 class="card__title"><a class="card__trigger card__click" href="#!">Tristique senectus et netus</a></h2>

        <div class="card__summary">
            
            <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque.</p>

        </div>

    </li>

    <li class="card__item">

        <div class="card__media">

            <img alt="" src="https://radancy.dev/placeholder-450x225.png">
    
        </div>

        <h2 class="card__title"><a class="card__trigger card__click" href="#!">Tristique et netus</a></h2>

        <div class="card__summary">
            
            <p>Habitant morbi tristique senectus et netus. Unde omnis iste natus error sit voluptatem accusantium doloremque.</p>

        </div>

    </li>

    <li class="card__item">

        <div class="card__media">

            <img alt="" src="https://radancy.dev/placeholder-450x225.png">

        </div>

        <h2 class="card__title"><a class="card__trigger card__click" href="#!">Fames ac turpis egestas</a></h2>

        <div class="card__summary">
                
            <p>Pellentesque habitant tristique senectus et malesuada fames ac egestas. Sed ut perspiciatis unde omnis iste error sit voluptatem doloremque.</p>

        </div>

    </li>

</ul>
/*!

  Radancy Component Library: Card
  Contributor(s):
  Michael "Spell" Spellacy

  Dependencies: Sass

*/

// Card (.card)

.card {

// Component Variables

--card-base-color: #6F00EF;
--card-columns: 3;
--card-horizontal-rows: 2;
--card-secondary-color: #767676;
--card-transition: 300ms;

$card-breakpoint: 60em;

display: grid;
gap: 1rem;
list-style: none;
margin: 0;
overflow-wrap: break-word;
padding: 0;
text-wrap: balance;

  &:not([data-horizontal]) {

    text-wrap: pretty;
  
    @media (min-width: 60em) {

      grid-template-columns: repeat(var(--card-columns), 1fr);

    }

  }

  // Item (.card__item)
  
  &__item {
  border: 1px var(--card-secondary-color) solid;
  display: grid;
  grid-template-rows: subgrid;
  padding: 1rem;
  position: relative; // Needed for pseudo-element trick.
  transition: var(--card-transition);

    &:has(> :nth-child(2):last-child) {
    grid-row: span 2;
    }

    &:has(> :nth-child(3):last-child), &:has(.card__block) {
    grid-row: span 3;
    }

    &:has(> :nth-child(4):last-child) {
    grid-row: span 4;
    }

    [data-horizontal] & {

      @media (min-width: $card-breakpoint) {

        gap: inherit;
        grid-auto-flow: column;
        grid-template-rows: min-content repeat(calc(var(--card-horizontal-rows) - 1), 1fr);
      
      }

    }
  
    &:focus-within, &:hover {
    background-color: #eee;
  
      .card__trigger {

        &::after {
        transform: translateX(calc(5em/16));
        }

      }

      .card__play {
      transform: scale(1.2);
      }
  
    }
      
  }

  // Block (.card__block) 

  &__block {
  display: grid;
  grid-row: inherit;
  grid-template-rows: subgrid;
  }

  // Media (.card__media)
  
  &__media {
  display: grid;

    [data-horizontal] & {

      @media (min-width: $card-breakpoint) {

        grid-row: span var(--card-horizontal-rows);
  
      }

    }

    picture {
    display: contents;
    }

    img, video {
    border-radius: 5px;
    border: calc(1rem / 16) var(--card-secondary-color) solid;
    display: block;
    grid-area: 1 / 1;
    inline-size: 100%;
    }

  }

  // Play Button (.card__play)

  &__play {
  grid-area: 1 / 1;
  inline-size: calc(40em / 16);
  margin-block-end: 1rem;
  margin-inline-end: 1rem;
  place-self: end;
  transition: var(--card-transition);

    path {
    fill: #fff;
    }

  }

  // Eyebrow (.card__eyebrow)

  &__eyebrow {
  color: var(--card-base-color);
  margin: 0;
  }

  // Title (.card__title)
  
  &__title {
  color: var(--card-base-color);
  font-size: calc(22.4rem / 16);
  line-height: normal;
  margin: 0;
  text-wrap: auto;
  width: fit-content;

    // If text is not a heading, then center it.
  
    &:not(h2):not(h3):not(h4):not(h5):not(h6) {
    justify-self: center;
    text-align: center;
    }
  
  }

  // Trigger (.card__trigger)

  &__trigger {
  background-color: transparent;
  border: 0;
  color: inherit;
  cursor: pointer;
  display: grid;
  font: inherit;
  gap: 0.3em;
  grid-auto-flow: column;
  margin: 0;
  padding: 0;
  text-decoration: none;

    &:visited {
    color: #000;
    }

    [data-remove-arrow] & {

      text-decoration: underline;

      &:focus, &:hover {
      text-decoration: none;
      }

    }
    
    // CTA
    // NOTE: HTML Entity being used, but you can use Font Awesome or inline SVG, too.

    &:not(button) {
  
      &::after {
      align-self: center;
      content: "\2794";
      display: block;
      line-height: 0;
      transition: var(--card-transition);

        [data-remove-arrow] & {

          display: none;

        }

      }

    }
  
  }

  // Summary (.card__summary)

  &__summary {

    p  {
    margin: 0;
    
      &:not(:last-child) {
      margin-block-end: 1rem;
      }

      &:last-child {
      margin-block-end: 0;
      }

    }

  }

  // CTA (.card__cta)

  &__cta {
  color: var(--card-base-color);
  font-weight: bold;
  }

  // Shared: Category (.card__category) & Metadata (.card__metadata)
  // TODO: Make part of grid; may need 12 column grid instead of 3. 

  &__category, &__metadata, &__links {
  display: flex;
  flex-wrap: wrap;
  font-size: 0.9rem;
  gap: 0.5rem;
  line-height: normal;
  list-style: none;
  padding: 0;

    li:not(:last-child)::after {
    padding: 0 0.5rem;
    }
  
  }

  // Category (.card__category)

  &__category {

    li {
    background-color: #ccc;
    border-radius: calc(5rem / 16);
    padding: 0.5rem 1rem 0.7rem;
    }
  
  }

  // Metadata (.card__metadata)

  &__metadata {

    li:not(:last-child)::after {
    content: "|";
    }

  }

  // Links (.card__links)

  &__links {

    a {
    background-color: var(--card-base-color);
    color: #fff;
    display: block;
    padding: 0.8rem;
    text-decoration: none;
  
      &:focus-visible, &:hover {
      text-decoration: underline;
      }
  
    }
  
  }

  // Pseudo-element Trick (.card__click)

  &__click {
  
    &::before {
    content: "";
    display: block;
    inset: 0;
    position: absolute;
    z-index: 1;
    }

  }
  
}

Best Practices for Creating Accessible Card Interfaces Anchor (Best Practices for Creating Accessible Card Interfaces)

When designing and developing card interfaces, it's essential to prioritize accessibility and usability. Below are key best practices and common pitfalls to avoid when structuring cards, especially for assistive technology users.

  • Use distinct and descriptive CTA text - Instead of generic phrases like "Learn More," opt for distinct and contextually relevant links (e.g., "Learn More about Accessibility"). This improves clarity and usability, especially for screen reader users.
  • Consider linking only the card heading - This approach, combined with affordances such as arrows, provides a familiar interaction pattern while reducing the risk of incorrectly programmed "Learn More" hyperlinks.
  • Utilize visually hidden text where appropriate - Hidden text techniques can enhance accessibility, but they should not be used in CMS-authored content, due to the high risk of breaking accessibility.
  • Use a "Faux CTA" if necessary - If "Learn More" must be included for design purposes, it can be visually present but not marked up as a hyperlink. It can even be hidden from assistive technology using ARIA, as it is often redundant. However, this technique should not be used in CMS-authored content.
  • Do not overload hyperlinks with excessive content - Wrapping too much content, including alternative text, within a link can overwhelm assistive technology users. If the entire card needs to be clickable, use the pseudo-element trick to maintain a single link structure.
  • Provide appropriate alt text for images - Only use alt text if the image requires a description. The card title should never be repeated as alt text.
  • Use proper heading levels - Card headings should be logical and follow the surrounding content structure. Typically these are h3 or h2 headings. Heading levels must also never be skipped.
  • Apply a class to card headings - This allows flexibility if the heading level needs to be changed later or replaced with a non-heading element.
  • Avoid headings in cards without summaries - If a card does not contain a summary, a heading should not be used.
  • Ensure proper focus states - The :focus pseudo-class does not work on non-interactive element for keyboard users. Instead, use :focus-within if you need to highlight parent element when a link within the card receives focus.
  • Be mindful of duplicate links - If both the heading and CTA link to the same URL, ensure they are correctly marked up to prevent confusion.
  • Provide semantic structure - Proper markup, like a list of cards with nested headings and links, may not requre you to add visually hidden content (though it is never a bad idea to include it anyway).

The pseudo-element Trick Anchor (The pseudo-element Trick)

The pseudo-element trick provides a simple CSS solution for making entire card regions clickable, eliminating the necessity for multiple hyperlinks or links wrapping various content regions. Additionally, it improves accessibility by enabling us to prioritize linking the title, the most distinct and unique aspect of a card.

.card__item {
position: relative;
}

.card__trigger::before {
content: "";
display: block;
inset: 0;
position: absolute;
z-index: 1;
}

Properties Anchor (Properties)

Use the following properties to configure this component.

Attribute Applies To Note Description
data-horizontal .card Boolean, Optional
Used to change the card layout to horizontal
data-remove-arrow .card Boolean, Optional
Used to remove the arrow affordance from all .card__trigger hyperlinks.

Class Applies To Note Description
.card article, div, ol, ul Mandatory
Used on the parent element that wraps your card content.
.card__item div, li Mandatory
Used on your card item. Must be a child of .card.
.card__block a Optional
Used to define the link used to wrap entire content regions. Should be used sparingly and when content output is minimal.
.card__category div Optional
Used to define a card category list.
.card__click a, button Optional
Used to determine which link or button in your card should contain the pseudo-element trick to expand the clickable region. Should only be used on one link or button per card.
.card__cta div Optional
Used to add distinct styles to your card's faux CTA.
.card__eyebrow p Optional
Used to define an additional card title. May only be used in hgroup.
.card__group hgroup Optional
Used to define multiple card titles, specifically when .card__eyebrow is used.
.card__media div Optional
Used to define your media region.
.card__metadata div Optional
Used to define a card metadata list.
.card__summary div Optional
Used to define a card summary.
.card__title h2, h3, h4, h5, h6, div Optional
Used to define primary card heading and may be applied to any logical heading or div if .card__summary is not present.
.card__trigger a, button Mandatory
Used to define a card interaction trigger.

Report Issues

Find a bug? Want a new feature? Report it on JIRA. You're so awesome!