ScreenshotNeo

BlogHow-to

How to Display Website Thumbnails in a Responsive Directory Grid

Build a responsive directory grid with fluid thumbnails, stable card layouts, responsive image sources, accessible alt text, and sensible lazy loading.

By the ScreenshotNeo team4 October 202610 min read

Use CSS Grid to make directory columns adapt to their container, then make each thumbnail fluid with width: 100%. Give images intrinsic dimensions to reserve space, choose object-fit: cover for consistent cropped cards or contain to show the whole screenshot, and use srcset plus sizes when you have image files at multiple widths. Lazy-load entries below the initial viewport; keep the prominent first image eager.

The layout and image-file selection solve different problems: CSS controls the displayed grid and image box, while srcset and sizes help the browser choose a suitable source file. sizes does not set the image’s display dimensions. See MDN’s responsive images guide.

1. Build a responsive directory grid

This complete HTML and CSS example uses a flexible minimum card width. The number of columns changes as the available space changes, including when the directory sits in a narrower container.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Website directory</title>
  <style>
    * { box-sizing: border-box; }
    body {
      margin: 0;
      padding: 1rem;
      font: 1rem/1.5 system-ui, sans-serif;
      color: #172033;
      background: #f5f7fb;
    }
    .directory {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
      gap: 1rem;
      max-width: 75rem;
      margin: 0 auto;
      padding: 0;
      list-style: none;
    }
    .site-card {
      min-width: 0;
      overflow: hidden;
      border: 1px solid #dce2ec;
      border-radius: .75rem;
      background: white;
    }
    .site-card a {
      display: block;
      color: inherit;
      text-decoration: none;
    }
    .site-card a:focus-visible {
      outline: 3px solid #245bdb;
      outline-offset: 3px;
    }
    .site-card img {
      display: block;
      width: 100%;
      height: auto;
      aspect-ratio: 8 / 5;
      object-fit: cover;
      object-position: center;
      background: #e9edf5;
    }
    .site-card span {
      display: block;
      padding: .75rem 1rem;
      font-weight: 650;
    }
  </style>
</head>
<body>
  <main>
    <h1>Websites</h1>
    <ul class="directory">
      <li class="site-card">
        <a href="https://example.com/">
          <img
            src="/images/example-640.jpg"
            srcset="/images/example-320.jpg 320w,
                    /images/example-640.jpg 640w,
                    /images/example-960.jpg 960w"
            sizes="(min-width: 78rem) 23rem,
                   (min-width: 42rem) calc((100vw - 3rem) / 2),
                   calc(100vw - 2rem)"
            width="960"
            height="600"
            loading="lazy"
            alt="Example site's home page"
          >
          <span>Example</span>
        </a>
      </li>
      <li class="site-card">
        <a href="https://example.org/">
          <img src="/images/example-org-640.jpg" width="960" height="600"
               loading="lazy" alt="Example.org's home page">
          <span>Example.org</span>
        </a>
      </li>
    </ul>
  </main>
</body>
</html>

Save the markup as an HTML file and replace the example links and image paths with your directory entries. The width and height values describe the source image’s intrinsic dimensions; they do not force the rendered image to that size. Keep the CSS fluid so the image fits its card.

Choose grid sizing behavior

  • repeat(auto-fit, minmax(..., 1fr)) fits as many columns as possible and lets the remaining columns expand to fill the row.
  • auto-fill also creates as many tracks as fit, but keeps empty tracks when the final row has fewer cards. This can preserve the expected column widths in that row.
  • The 16rem minimum is a design choice. Increase it for larger cards or decrease it for denser directories. min(100%, 16rem) prevents the minimum from overflowing a container narrower than 16rem.
  • The gap controls both row and column spacing. Use two values, such as gap: 1.5rem 1rem, if vertical and horizontal spacing should differ.

The example’s sizes values are estimates for a page with 1rem body padding, a 75rem content maximum, and roughly two columns at tablet widths. Recalculate them if your gutters, maximum width, or minimum card size differ. They tell the browser the expected CSS slot width; they do not change the grid.

2. Decide whether thumbnails crop or show the full page

A directory needs predictable card geometry, but screenshots and logos may need to remain fully visible. Pick the treatment based on what the thumbnail communicates.

Rule Result Good fit
object-fit: cover Fills the box and crops image edges as needed. Consistent screenshot cards where a uniform tile matters.
object-fit: contain Shows the complete image; unused space can remain inside the box. Logos or page previews where clipping would hide important content.
object-position: 50% 20% Moves the visible area toward the specified point when cropping. Screenshots whose important header or focal content sits near the top.

For a full screenshot, contain can make cards look uneven if the source aspect ratios vary. Give the image box a deliberate background so the unused area looks intentional:

.site-card img {
  display: block;
  width: 100%;
  aspect-ratio: 8 / 5;
  height: auto;
  object-fit: contain;
  background: #eef1f6;
}

/* Keep the top of a page visible in a cropped thumbnail. */
.site-card img.page-preview {
  object-fit: cover;
  object-position: center top;
}

Do not set a fixed width and height that stretches the image. Use a ratio and a suitable object-fit rule instead. The web.dev responsive images guide explains how fluid images, aspect ratios, and fitting behavior work together.

3. Serve the right image size

If each screenshot exists only as one file, a fluid img is enough to make it fit the card, though a large source may waste bandwidth on small cards. If you generate multiple widths, provide them as width-descriptor candidates in srcset, then describe the expected slot in sizes.

<img
  src="/thumbs/site-640.webp"
  srcset="/thumbs/site-320.webp 320w,
          /thumbs/site-640.webp 640w,
          /thumbs/site-960.webp 960w"
  sizes="(min-width: 78rem) 23rem,
         (min-width: 42rem) calc((100vw - 3rem) / 2),
         calc(100vw - 2rem)"
  width="960"
  height="600"
  alt="Site name's home page"
>
  • Each 320w, 640w, or 960w descriptor is the candidate file’s actual pixel width. Keep the descriptors accurate.
  • src provides a fallback source. The browser can select a candidate from srcset based on the slot estimate, display density, and other browser considerations.
  • sizes should reflect the CSS slot width at your actual breakpoints and gutters. It is a hint for source selection, not a CSS width declaration.
  • Retain width and height to communicate the source ratio before download. If candidates share an aspect ratio, the ratio remains consistent.

Use <picture> when the image content should change, for example when mobile uses a separately composed crop. For ordinary resolution changes of the same composition, srcset and sizes are usually simpler. See MDN’s responsive images guide and web.dev’s images guide.

4. Set loading behavior and useful alt text

Native lazy loading lets the browser defer images that are well outside the visible area. Use it for a long directory’s lower cards, but do not automatically lazy-load a prominent image near the top of the page. That image may be important to the initial rendering.

<!-- Lower directory card: suitable for lazy loading. -->
<img src="/thumbs/another-site.webp" width="960" height="600"
     loading="lazy" alt="Another site's home page">

<!-- Prominent first image: load eagerly. -->
<img src="/thumbs/featured-site.webp" width="960" height="600"
     loading="eager" fetchpriority="high"
     alt="Featured site's home page">

Only give high fetch priority to an image that is genuinely important to the initial view. Many directory thumbnails do not need it. For alternative text:

  • Describe what the screenshot adds, such as alt="Example site's home page showing its product catalog", when that visual information matters.
  • If the linked card’s visible text already names the destination and the thumbnail adds no useful information, use alt="" so assistive technology can treat it as decorative.
  • Include the alt attribute even when its value is empty. Do not repeat the card name in both the image description and link text unless that repetition helps users.

Accurate dimensions also reserve image space and reduce layout movement while bytes load. These loading and alternative-text recommendations follow web.dev’s responsive images guidance.

5. Generate and maintain directory thumbnails

The grid expects image URLs that resolve to usable image files. If screenshots come from your own capture pipeline, save a stable thumbnail derivative for each directory item and keep its dimensions and format consistent with the markup. An automated directory should also handle entries whose source page cannot produce a usable image.

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its API can capture a page into PNG, JPEG, or WebP, and its options include full-page capture, viewport and device presets, custom CSS or JavaScript, selector waits, and caching. The API supports signed links for public <img> tags and bulk capture of up to 100 URLs per call. See the ScreenshotNeo documentation for request parameters and response behavior.

6. Or skip the browser setup

For one screenshot, a single GET request can return an image. This cURL example saves a WebP file:

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp

Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));

Replace YOUR_API_KEY with your key and change the target URL. Keep the key on a server, not in public page markup. Consult the ScreenshotNeo API documentation for available capture options. Cookie banners, popups, and chat widgets are removed before capture; bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000.

Sign up for ScreenshotNeo’s free plan to create an API key and start capturing screenshots.

7. Troubleshooting

Symptom Likely cause Fix
Cards overflow a narrow column or mobile screen. The grid’s minimum track is wider than its container, or a child has an intrinsic minimum width. Use minmax(min(100%, 16rem), 1fr) and set min-width: 0 on cards. Check page padding and long unbroken text.
All thumbnails download at the largest size. sizes overstates the card width, width descriptors are wrong, or the grid’s actual CSS differs from the hint. Verify each file’s real pixel width and recalculate sizes from the rendered card width and gutters. Remember that sizes does not control CSS layout.
Images are blurry on high-density displays. The candidate set has no sufficiently large source for the rendered slot and device density. Generate a larger source candidate and include its accurate width descriptor in srcset.
Page content jumps when thumbnails load. Image dimensions or an aspect ratio are missing, so the browser cannot reserve the final space. Set the correct intrinsic width and height attributes and keep the image fluid in CSS.
Important content is cut off. cover crops the source to fill the card box. Use contain, change the box ratio, or adjust object-position to move the crop toward the content.
Some cards show broken image icons. The file URL is invalid, inaccessible, or its screenshot generation failed. Check the URL and response in the browser’s network panel, provide a fallback image, and make the directory data indicate when a thumbnail is unavailable.
The first visible thumbnail appears late. It may have been marked lazy or assigned low priority despite being prominent. Use eager loading for the important initial image, and consider fetchpriority="high" only for that image.

8. Performance, reliability, and cost

  • Reduce image bytes: offer width candidates that match actual card sizes, and prefer an appropriate compressed image format your serving setup supports. Avoid sending a very large screenshot to every small tile.
  • Prevent layout movement: provide dimensions and use a consistent ratio when card geometry should remain uniform.
  • Load only what is needed: lazy-load below-the-fold cards. For very large directories, also paginate or incrementally render entries so the browser does not need to process every card at once.
  • Keep screenshot results reliable: treat a missing, blank, or failed capture as a data state. Use a placeholder or retry policy appropriate to your application, and avoid presenting an unsuccessful capture as a valid preview.
  • Account for both capture and delivery: image generation and serving are separate parts of a directory pipeline. Use cached thumbnails when the underlying page has not changed, and avoid recapturing the same URL for every visitor.
  • Know capture billing: ScreenshotNeo says only clean shots are billed; bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. Responses identify the page verdict and billing status in headers. Its listed monthly plans are Free: 1,000 shots at no charge; Starter: $5 for 3,000; Growth: $15 for 15,000; Pro: $39 for 60,000; Scale: $99 for 250,000; and Business: $249 for 1,000,000. Yearly billing gives two months free, and every feature is available on every plan. Confirm current details in the ScreenshotNeo product information.

FAQ

Should a directory use CSS Grid or Flexbox?

Grid is a direct fit when cards form rows and columns and should share track sizing. Flexbox can suit a one-dimensional row that wraps; either can be responsive, but this pattern uses Grid for explicit two-dimensional tracks.

Can I use srcset without sizes?

You can, but width-descriptor candidates work best when the browser also has a realistic slot-size hint. Keep sizes aligned with the layout your CSS actually creates.

Does loading="lazy" stop a browser from downloading every directory image?

It can defer offscreen image requests, but it does not remove cards from the document or replace pagination for very large result sets.

If the directory card represents that site, linking the card to the destination is a common pattern. Make the link’s accessible name clear through visible text and appropriate image alternative text.

Sources