ScreenshotNeo

BlogGuides

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.

By the ScreenshotNeo team1 October 20268 min read

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 alt text. 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-ratio or 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-fit are layout features handled by the browser; the main cost is image decoding and transfer.
  • Reserve space with dimensions or aspect-ratio to 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.