Skip to content

Case study card

How
An <sdc-card> holding full-bleed art above a body grid. The headline contains the only link, .sdc-card__link, whose ::after stretches over the whole card so all of it clicks.
When
To put one case study forward as a single claim. One card makes one argument: a headline that could stand alone, a sentence of support, two facts, one way in.
Where
Inside the home page deck. The work index uses rows instead, because a list of fourteen things is a list, not a deck.
Section titled “Why the link is the headline, not the card”

The obvious build is to wrap the whole card in an anchor. It was built that way first, and it was wrong.

A link’s name comes from its content. Wrapping everything means the link announces the headline, the paragraph, both meta labels and their values, and the call to action, as one long string. Replacing that with a written label is worse: the name then no longer matches the words on screen, which breaks voice control and was flagged by an audit.

The stretched link solves both. The headline is the link, so its name is exactly the visible headline. An invisible layer extends its click area over the card, so nothing changes for a mouse. Hover and the focus ring move to the card through :has(), so the card still responds as one object.

Art sits at the top, 16 by 9, full bleed, cropped to fill. A logo lockup is the exception: it fits inside the box instead of cropping, because cropping a row of marks cuts marks off.

Headline is display type and carries the link. It is the argument, not a label. “Nobody asked for this one.” not “Merchant Flow Builder”.

Text is one or two sentences of support.

Meta is two pairs, Product and Objective. Not Client and Service: the work is described by what it was and what it was for, not by who paid.

Call to action reads “Read case study” and is plain text, not a second link. Two links to the same place in one card is a duplicate for anyone navigating by links.

Card height is capped so the deck can pin a card to the viewport. Copy that runs long does not overflow; it trips a fit check and drops the whole deck into the plain list fallback. The Plate card is 18 pixels over on a 600px-tall phone today, which is why that size gets the list.

Keep the text to two sentences. Keep the objective under about six words.

The card’s accessible name is the headline alone. The arrow in the call to action is aria-hidden, because “arrow” in a name is noise.

Art is decorative: alt="" always. The headline already says what the card is about, so describing the picture repeats it.

Do

  • Write a headline that works with nothing around it.
  • Keep the stretched link on the headline.
  • Give art empty alt text.

Don’t

  • Wrap the whole card in an anchor.
  • Make the call to action a second link.
  • Let the copy run past two sentences.
  1. A screen reader announces the card as its headline and nothing else.
  2. Clicking the picture opens the case study.
  3. Tab to it: the ring is around the card, not just the headline text.
  4. At 402 by 660 the deck still scrolls rather than falling back to a list.

src/components/ui/CaseStudyCard.astro, rendering <sdc-card>. The home page maps its case list through it; drawn art goes in the art slot.

---
import CaseStudyCard from '../components/ui/CaseStudyCard.astro';
import JourneyMap from '../components/art/JourneyMap.astro';
---
<CaseStudyCard href="/work/paypal" head="The checkout dead end." text="…" product="PayPal consumer checkout" objective="Show leadership where the dead end starts" art="journey">
<JourneyMap slot="art" />
</CaseStudyCard>
Prop Type Default What it does
href string required Where the card goes. The link is the only interactive element in the card.
head string required The claim. Becomes the h3 and the link text.
text string required One paragraph under the claim.
product string required The first meta pair.
objective string required The second meta pair.
art 'image' | 'plugin' | 'checkout' | 'journey' required Which art treatment the card carries.
image string unset Photographic art. Decorative, so it carries no alt text; the claim carries the meaning.
contain boolean false Fit the art inside the area instead of cropping it.
field string unset The art’s own ground color, when it should match the artwork.