skip to content

In next/image, when do you reach for the `fill` prop instead of width and height, what must be true of the parent element, and why does a `fill` image usually also need a `sizes` prop?

level: middleimportance: should knowfreq 62%

answer

  1. absolutely positioned, so the parent owns the box
  2. nearest positioned ancestor, with a real height
  3. no layout width means no good candidate choice
  4. the silent fallback is full viewport width
  5. object-fit decides crop versus letterbox

basics

~20 s

Use fill when the intrinsic dimensions are unknown; it absolutely positions the image to fill the nearest positioned ancestor, which must have position: relative and a real height. sizes then tells the browser which srcset candidate to download.

solid answer

~50 s

`fill` is for the case where you do not know — or do not care about — the image's intrinsic size, typically a CMS-driven photo dropped into a fixed card or a hero band. Instead of computing an aspect-ratio box, Next absolutely positions the `<img>` to cover its container, so the container has to be a positioned element (`position: relative`, `absolute` or `fixed`) with a height of its own; otherwise the image collapses to nothing. You then control cropping with `object-fit` in `style` or a class. The catch is that an absolutely positioned image gives the browser no layout width to reason about, so without `sizes` Next falls back to `sizes="100vw"` — the browser assumes full viewport width and pulls a candidate far larger than a 300px card needs. Passing an honest `sizes` such as `"(max-width: 768px) 100vw, 300px"` is what makes the responsive srcset actually save bytes.

code

jsx · 15 lines
jsx
import Image from 'next/image';

export function Thumbnail({ photo }) {
  return (
    <div style={{ position: 'relative', width: 300, height: 200, overflow: 'hidden' }}>
      <Image
        src={photo.url}
        alt={photo.alt}
        fill
        sizes="(max-width: 640px) 100vw, 300px"
        style={{ objectFit: 'cover' }}
      />
    </div>
  );
}

go deeper

for a junior

Remember the two mutually exclusive ways to size a next/image: intrinsic width and height, or fill inside a parent you have positioned and given a height.

for a middle

Explain why fill changes the layout contract — the image goes out of flow, the parent must supply the box, object-fit decides cropping, and sizes replaces the layout width the browser no longer has.

for a senior

Show you audit sizes values against the real CSS at each breakpoint, and can point to the oversized-download symptom that a copy-pasted 100vw produces on a card grid.

for a principal

Own the convention: decide where in your component library fill is allowed at all, and make the sizes string a required prop of the wrapper so product teams cannot silently ship full-viewport downloads.

## Two ways to tell Next how big the box is `next/image` must reserve layout space before the file arrives. There are exactly two ways to give it what it needs: 1. **`width` and `height`** — you state the intrinsic dimensions, Next derives the aspect ratio and emits sizing styles. The element keeps its ratio while CSS scales it. 2. **`fill`** — you state nothing, and Next absolutely positions the image to fill whatever box its parent already defines. ```jsx <div style={{ position: 'relative', height: 200 }}> <Image src={photo.url} alt={photo.alt} fill sizes="(max-width: 768px) 100vw, 300px" style={{ objectFit: 'cover' }} /> </div> ``` ## When `fill` is the right choice Reach for it when the *source* dimensions are unknown at author time but the *box* is known from CSS: user-uploaded or CMS images, avatars in a fixed circle, cards in a grid, a hero band that is always 40vh. Reach for `width`/`height` when you do know the intrinsic size — especially for a static import, where Next reads the dimensions from the file for you and you should not be using `fill` at all. ## The parent contract Because the image is absolutely positioned, it is laid out against its **nearest positioned ancestor**. Two things must hold: - that ancestor is positioned (`relative` is the usual choice); - that ancestor has a height that does not depend on the image. The second one is where the common bug lives. An absolutely positioned child contributes nothing to its parent's height, so a `<div style={{ position: 'relative' }}>` with no height wrapping a `fill` image renders a zero-height container and an invisible image. If the parent is not positioned at all, the image escapes further up the tree and stretches across some ancestor you did not intend — usually the whole page. Since the image no longer has an aspect-ratio box, it will stretch to the container's shape unless you say otherwise. `object-fit: cover` (crop to fill) or `object-fit: contain` (letterbox) is effectively part of the pattern, along with `overflow: hidden` on the parent if you are cropping. ## Why `sizes` matters more here than anywhere else The `srcset` Next generates is a menu of widths. The browser picks from that menu using the layout width of the element, its device pixel ratio, and the `sizes` attribute. With `width`/`height` and no `sizes`, Next can emit a fixed-size srcset (1x/2x) around the declared width, which is usually fine. With `fill`, there is no declared width, and the element is out of flow, so the browser has to choose a candidate *before* layout tells it anything useful. Next's fallback is `sizes="100vw"`, which is a claim that the image spans the whole viewport. On a 1440px desktop with a 2x screen, that pushes the browser toward the largest candidate available — for an image that renders in a 300px card. Users pay in bytes and in decode time, and the page often gets a worse LCP than a plain `<img>` would have. A correct `sizes` is just a media-query list describing the element's rendered width per breakpoint: ```jsx sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 300px" ``` Read it as: below 640px this image is the full viewport width; below 1024px it is half; otherwise it is 300 CSS pixels. Providing `sizes` also tells Next to generate candidates across the full configured width range rather than a narrow fixed-size pair, so the browser has genuinely useful options to choose from. ## How this goes wrong in review - `sizes="100vw"` copy-pasted onto a thumbnail grid. It is syntactically fine and silently downloads the largest variant for every tile. - `fill` used on a statically imported asset whose dimensions were already known — you gave up the free aspect ratio for nothing. - A parent with `position: relative` but height driven by its (absolutely positioned) child, producing an empty box. - `sizes` describing the intended design rather than what the CSS actually does; it is a hint the browser trusts, and a wrong hint is worse than a rough-but-honest one. The one-line rule: `fill` delegates the box to CSS, and once CSS owns the box, `sizes` is how you tell the browser what CSS decided.

  • What visually happens when the parent of a `fill` image has `position: relative` but no height?
    The container renders at zero height and the image is invisible. An absolutely positioned child is out of flow, so it contributes nothing to its parent's height — the parent must get its height from something else: a fixed value, an aspect-ratio rule, padding, or a grid or flex track. This is the single most common `fill` bug and it produces no console error.
  • Is `sizes` a promise the browser enforces, or a hint?
    A hint, and the browser trusts it. It uses `sizes` together with device pixel ratio to pick a srcset candidate before layout is final, so a wrong value is not corrected later — an inflated `sizes` just means an oversized download, and an understated one means a blurry image. Describe what your CSS actually does at each breakpoint rather than what you wish it did.
  • Would you use `fill` for a statically imported logo?
    No. A static import already gives Next the intrinsic width and height, so plain `width`/`height` (or the bare import with no dimensions) reserves the correct aspect-ratio box with no CSS contract to honour. `fill` would throw away that information and add a positioned-parent requirement for nothing.

saying these in an interview costs you the question

  • Thinks fill means the image scales itself to its content
  • Omits sizes and assumes the srcset still saves bytes
  • Uses fill without positioning or sizing the parent
  • Believes object-fit is applied automatically by fill
  • Sets sizes to 100vw on every image as a safe default

context