Modern linked card with actions

Eric Bailey wrote a great article for Piccalilli, Accessible faux-nested interactive controls . When it comes to wordsmithing, I’m no Eric Bailey, so I’m saving it here on my site as “Modern linked card with actions”.

I recommend reading the article for all the thought behind this, both from Eric and from Andy’s original solution. I’ve implemented this in a handful of ways and I dig this one, so I thought I’d stash it here.

If BEM is something you’re into, here’s the basics:

<article class="clickable-card">
  <h3>
    <a class="card-link" href="/destination">
      Card title
    </a>
  </h3>

  <p>Optional supporting content.</p>

  <div class="card-actions">
    <button type="button">Action</button>
    <a href="/secondary">Secondary link</a>
  </div>
</article>
/* Establish positioning context */
.clickable-card {
  position: relative;
}

/* Stretch the primary link */
.card-link {
  position: static;
}

.card-link::before {
  content: "";
  position: absolute;
  inset: 0;
}

/* Keep nested interactive elements clickable */
.clickable-card
  :is(
    a,
    button,
    input,
    select,
    textarea,
    summary,
    [role="button"],
    [tabindex]:not([tabindex="-1"])
  ):not(.card-link) {
  position: relative;
  z-index: 1;
}

/* Optional focus ring */
.clickable-card:focus-within {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

If you’re trudging your way through the React ecosystem, here’s a way to handle it with JSX.

function ClickableCard({ href, title, children, actions }) {
  return (
    <article className="clickable-card">
      <h3>
        <a className="card-link" href={href}>
          {title}
        </a>
      </h3>

      {children}

      {actions && (
        <div className="card-actions">
          {actions}
        </div>
      )}
    </article>
  );
}

And some example usage:

<ClickableCard
  href="/destination"
  title="Card title"
  actions={
    <>
      <button type="button">Action</button>
      <a href="/secondary">Secondary</a>
    </>
  }
>
  <p>Supporting content lives here.</p>
</ClickableCard>