How to make a card
Short answer
An image, text and a button in a block with rounding and a shadow:
<style>
.card { width: 220px; border-radius: 12px; overflow: hidden; background: white; box-shadow: 0 2px 10px rgb(0 0 0 / 15%); }
.card img { display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover; }
.card .body { padding: 12px; }
.card h3 { margin: 0 0 6px; font-size: 18px; }
.card p { margin: 0 0 12px; color: dimgray; font-size: 14px; }
</style>
<article class="card">
<img src="/media/photo-400x250.jpg" alt="Lake in the mountains">
<div class="body"><h3>Alpine Lakes Tour</h3><p>Five days of hiking and fresh air.</p><button>From $299</button></div>
</article>overflow: hidden clips the image corners to the card's rounding, and object-fit: cover fits photos of any proportions to the same height.
The whole card as a link
To make the whole card clickable, stretch the link's pseudo-element over all of it:
<style>
.c { position: relative; width: 220px; padding: 12px; border: 1px solid gainsboro; border-radius: 12px; }
.c a::after { content: ""; position: absolute; inset: 0; }
.c:hover { border-color: royalblue; }
</style>
<div class="c"><h3 style="margin: 0;"><a href="https://example.com">Read the guide</a></h3><p style="margin: 6px 0 0;">Ten tips for beginners.</p></div>This keeps only the heading as the link, so a screen reader reads short clear text instead of the whole card.
Common mistake
People wrap the whole card, button included, in an a. A button inside a link is invalid markup, and clicking it behaves unpredictably. Use the stretched ::after trick.
Functions used in this recipe
- <article> — A self-contained piece that makes sense on its own: an article, a post, a review, a product card.
- border-radius — Rounds the corners of an element.
- box-shadow — A shadow around the element's box.
- object-fit — How the picture of an `<img>` or `<video>` fits into the element's box.