Image Card in CSS: Responsive Layouts, Object Fit, and Accessible Markup
Build responsive CSS image cards with semantic HTML, object-fit, aspect-ratio, accessible text, and reliable fixes for cropping and layout issues.
An image card in CSS combines an image, a heading, supporting content, and optionally a footer inside a reusable component. Use semantic HTML, CSS Grid for the card collection, and object-fit to decide whether images crop, letterbox, or stretch. For most card grids, object-fit: cover with a fixed or ratio-based image area gives consistent cards without distortion.
MDN describes a card as a component containing a heading, image, content, and optional footer. See the MDN card recipe for the underlying layout pattern.
1. A complete responsive image-card example
The following example is runnable as a single HTML file. It uses an article for each card, meaningful alternative text, a responsive Grid, and a consistent image region.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Responsive image cards</title>
<style>
:root {
font-family: system-ui, sans-serif;
color: #172033;
background: #f4f6f9;
}
* { box-sizing: border-box; }
body {
margin: 0;
padding: 2rem;
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
gap: 1.25rem;
max-width: 1100px;
margin: 0 auto;
}
.card {
display: grid;
grid-template-rows: max-content 12rem 1fr max-content;
overflow: hidden;
border: 1px solid #d6dbe5;
border-radius: 0.75rem;
background: #fff;
box-shadow: 0 0.25rem 1rem rgb(23 32 51 / 8%);
}
.card header,
.card__content,
.card footer {
padding: 1rem;
}
.card h2 {
margin: 0;
font-size: 1.15rem;
line-height: 1.25;
}
.card img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.card__content p { margin: 0; line-height: 1.55; }
.card footer { border-top: 1px solid #edf0f4; color: #596579; font-size: 0.9rem; }
</style>
</head>
<body>
<main>
<h1>Featured places</h1>
<section class="cards" aria-label="Featured places">
<article class="card">
<header><h2>Alpine lake</h2></header>
<img src="lake.jpg" alt="Blue lake surrounded by snow-covered mountains">
<div class="card__content">
<p>A clear lake beneath a mountain ridge, popular for day hikes.</p>
</div>
<footer>Travel guide</footer>
</article>
<article class="card">
<header><h2>Coastal trail</h2></header>
<img src="coast.jpg" alt="Cliffside walking trail above a rocky coast">
<div class="card__content">
<p>A windswept route with views across the open water.</p>
</div>
<footer>Outdoor guide</footer>
</article>
</section>
</main>
</body>
</html>
2. Choose the right image fitting mode
The image’s intrinsic aspect ratio rarely matches the card’s image box. The CSS object-fit property controls how the replaced element is fitted.
| Value | What it does | Use it when | Trade-off |
|---|---|---|---|
cover |
Fills the entire box while preserving the source ratio. | Uniform card heights matter and cropping is acceptable. | Edges can be cropped. |
contain |
Shows the complete image inside the box. | Every part of a product, diagram, artwork, or screenshot must remain visible. | Empty bars can appear; set a background color. |
fill |
Fills both dimensions independently. | Almost never for content images. | It can stretch and distort the image. |
none |
Keeps the source’s intrinsic size. | Rare, highly controlled layouts. | Overflow and inconsistent card geometry are likely. |
.card img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: 50% 50%;
}
.product-card img {
background: #f1f3f5;
object-fit: contain;
padding: 1rem;
}
With cover, adjust object-position when the important subject is near an edge: object-position: 70% 20% keeps a subject toward the upper-right in view. Check the crop at narrow widths; a focal point that looks good on desktop can disappear on a phone.
3. Keep proportions predictable with aspect-ratio
The aspect-ratio property reserves a consistent shape before an image finishes loading. It is useful when cards should have a 16:9, 4:3, or square media area.
.card__media {
aspect-ratio: 4 / 3;
overflow: hidden;
background: #e9edf2;
}
.card__media img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
Use the wrapper when you need rounded corners or an overlay. Put overflow: hidden on that wrapper so the image follows the card’s radius.
4. Make a collection responsive with CSS Grid
This common pattern creates as many columns as fit and lets each column shrink to a chosen minimum:
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
gap: 1.25rem;
}
The 230px minimum is a design starting point from MDN’s example, not a browser requirement. Increase it for text-heavy cards or decrease it only after checking the narrowest supported viewport. Use auto-fit when you want empty tracks to collapse and existing cards to expand on wide screens.
Grid is convenient for equal collections. Flexbox is also suitable when cards should flow in a row and wrap; MDN documents it as an alternative in the card recipe. Keep the card itself as a grid when a heading, media row, content row, and footer should line up consistently.
5. Accessible image-card markup
- Use an
<article>when each card is a self-contained item in a collection. - Give informative images concise, specific
alttext. Describe what the image communicates, not its file name. - Use
alt=""for purely decorative images so assistive technology skips them. - Use a real heading such as
<h2>inside each card, with heading levels matching the page outline. - Do not put essential text only in a background image; CSS backgrounds have no useful image alternative.
- If the entire card is a link, make the link target and accessible name clear. Avoid nesting interactive controls inside another link.
6. When a CSS background is the better choice
Use an <img> for meaningful content. Use a CSS background for decoration, such as a texture behind text. MDN’s background documentation covers background-size and background-position.
.hero-card {
min-height: 18rem;
padding: 2rem;
color: white;
background-image: linear-gradient(rgb(0 0 0 / 35%), rgb(0 0 0 / 65%)),
url("mountains.jpg");
background-size: cover;
background-position: center;
}
.icon-card {
background-image: url("pattern.svg");
background-size: contain;
background-repeat: no-repeat;
background-position: right bottom;
}
background-size: cover fills the box and can crop. contain preserves the whole background and can leave gaps. Neither supplies an image alternative, so do not use this approach for information a reader needs.
7. Images, loading, and visual stability
- Set an
aspect-ratioor explicit dimensions so loading does not move surrounding content. - Use appropriately sized image files and modern formats where your delivery pipeline supports them.
- Use
loading="lazy"for cards below the initial viewport; keep the first visible card eager when it is part of the page’s primary content. - Add
decoding="async"when asynchronous decoding suits your page, but still reserve the media space. - Provide a useful fallback background color for failed or delayed images.
<div class="card__media">
<img
src="lake.webp"
width="1200"
height="900"
loading="lazy"
decoding="async"
alt="Blue lake surrounded by snow-covered mountains"
>
</div>
8. Troubleshooting image cards
| Symptom | Likely cause | Fix |
|---|---|---|
| Images stretch | height and width are forced without a fitting rule. |
Set the media box dimensions and use object-fit: cover or contain. |
| Cards have different image heights | The image’s intrinsic dimensions determine layout. | Give the wrapper an aspect-ratio or fixed grid row, then set the image to height: 100%. |
| Important subject is cut off | cover crops overflow from the wrong side. |
Change object-position, use a different crop, or switch to contain. |
| Blank bars appear | contain preserves the source ratio. |
Choose a deliberate background color, or use cover if cropping is acceptable. |
| Rounded corners do not clip the image | The image overflows the rounded parent. | Apply overflow: hidden to the element with the border radius. |
| Grid overflows on phones | The minimum track width is wider than the available space. | Lower minmax()‘s minimum, add page padding carefully, or use a small-screen media query. |
| Cards jump while loading | No reserved media space. | Add aspect-ratio or width/height attributes. |
| Screen readers announce useless text | Decorative imagery has descriptive alt text. | Use an empty alt attribute for decorative images. |
| Background image contains essential information | CSS background is not exposed as meaningful content. | Use an <img> with an appropriate alternative instead. |
9. Testing checklist
- Resize from a wide desktop viewport to the narrowest supported phone width.
- Test portrait and landscape source images.
- Confirm the focal subject remains visible with
cover. - Disable images or simulate a failed request and check that the card still makes sense.
- Navigate headings and links with a keyboard and inspect the card with a screen reader.
- Check long headings and descriptions so they do not overflow or create unusable controls.
- Verify loading placeholders do not shift neighboring cards.
10. Or skip the browser setup
If you need rendered previews of your card page for documentation, review, regression checks, or an image catalog, ScreenshotNeo captures the URL through a website screenshot API. It can capture a full page or one element by CSS selector, wait for a selector or network idle, run custom CSS or JavaScript, choose a viewport and retina scale, and return PNG, JPEG, WebP, or PDF. Read the ScreenshotNeo API documentation for all options.
# cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com/cards \
-o cards.webp
# Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com/cards"},
timeout=90,
)
r.raise_for_status()
open("cards.webp", "wb").write(r.content)
// Node.js
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com/cards'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const buffer = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('cards.webp', buffer);
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing result. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000.
Create a free ScreenshotNeo account and start with the 1,000 monthly screenshots.
11. Performance, reliability, and cost notes
- CSS Grid and
object-fitare layout features handled by the browser; the main cost is image decoding and transfer. - Reserve space with dimensions or
aspect-ratioto reduce layout movement. - Keep card images close to the rendered size and lazy-load below-the-fold content.
- For automated screenshots, wait for the card media or a stable selector instead of relying on an arbitrary short delay.
- Use caching when the same card page is captured repeatedly; choose a TTL that matches how often the page changes.
- For private pages, pass the required headers, cookies, user agent, or Authorization values through the capture service rather than exposing them in client-side card code.
12. FAQ
Should an image card use img or a background?
Use img when the image conveys information. Use a background for decoration behind content.
Is cover better than contain?
Neither is universally better. Choose cover for a filled, uniform frame and contain when the complete source must remain visible.
Can I make every card the same height?
Yes. Use a consistent media row and a card grid such as grid-template-rows: max-content 12rem 1fr max-content. Also test unusually long text.
Why does height: auto not create equal cards?
It preserves each image’s natural ratio, so different source dimensions produce different heights. Give the media area a ratio or fixed row when uniformity is required.
How do I keep a face or product centered?
Adjust object-position, for example object-position: 65% 35%, and verify the result at each responsive breakpoint.


