
object-fit and object-position: Controlling Images Like a Pro
Every content-driven site eventually hits the same problem. The design calls for a neat grid of identical thumbnails, but the images coming out of the CMS are portraits, panoramas, screenshots, and phone photos in every shape imaginable. Force them into the same box with width and height and they stretch like funhouse mirrors. object-fit and object-position are the two properties that solve this, and once you understand them, you'll reach for them constantly.
This guide covers every object-fit value, how object-position controls the crop, and the practical patterns I use for cards, hero banners, avatars, logos, and video.
What Are Replaced Elements?
Both properties apply to replaced elements: elements whose content comes from an external resource rather than from CSS. In practice that means:
<img><video>(including itsposterframe)- other embedded content such as
<object>, though in practice you'll use these properties almost entirely on images and video
An <iframe> is technically a replaced element too, but it has no natural size or ratio of its own, so object-fit has nothing to work with there.
A replaced element has two boxes to think about. There's the element box, the rectangle CSS lays out on the page with width and height, and there's the content, the actual image pixels with their own natural width and height. By default, the browser stretches the content to fill the element box exactly, whatever the ratio. object-fit changes how the content is sized inside that box, and object-position changes where it sits.
The Five object-fit Values
Let's use one setup for all the examples: a 300×200 box (3:2) showing a tall portrait photo.
<img
class="thumb"
src="/images/portrait.jpg"
width="800"
height="1200"
alt="Portrait of a violinist on stage"
/>
.thumb {
width: 300px;
height: 200px;
}
fill (the default)
.thumb {
object-fit: fill;
}
The image is stretched to exactly 300×200, ignoring its natural ratio. For our portrait, the violinist becomes short and wide. This is almost never what you want for photos, and it's why this property exists at all.
contain
.thumb {
object-fit: contain;
}
The image is scaled so the whole thing fits inside the box while keeping its ratio. Our portrait is scaled down to about 133×200 and centered, leaving empty bands on the left and right. Those bands show the element's background, so you can style them:
.thumb--contain {
object-fit: contain;
background: #0f172a;
}
Use contain when nothing may be cropped: product shots, logos, diagrams, and screenshots.
cover
.thumb {
object-fit: cover;
}
The image is scaled so it completely covers the box while keeping its ratio, and whatever spills outside gets clipped. Our portrait is scaled to 300×450 and the top and bottom are cropped off. This is the workhorse value for thumbnails, hero images, and backgrounds. No empty space and no distortion.
none
.thumb {
object-fit: none;
}
The image isn't resized at all. It renders at its natural size, centered, and clipped to the box. For a large photo in a small box, you'll see a zoomed-in slice from the middle. It's occasionally handy for pixel art, sprites, or a deliberate "detail crop" effect.
scale-down
.thumb {
object-fit: scale-down;
}
The browser compares none and contain and uses whichever produces the smaller image. Large images shrink to fit and small images stay at their natural size instead of being blown up and blurry. This is the best choice for user-uploaded logos, where you can't predict the file size and upscaling a tiny PNG looks terrible.
Controlling the Crop with object-position
When the content is smaller or larger than the box, which happens with contain, cover, none, and scale-down, object-position decides how it's aligned. The default is 50% 50%, which means centered.
The syntax mirrors background-position:
.a {
object-position: center;
} /* default */
.b {
object-position: top;
} /* keep the top edge */
.c {
object-position: left bottom;
}
.d {
object-position: 30% 20%;
} /* percentage alignment */
.e {
object-position: right 1rem top 2rem;
} /* edge offsets */
.f {
object-position: 0 0;
} /* same as left top */
How Percentages Work
Percentages in object-position don't mean "move the image by 30%". A value of 30% 20% aligns the point 30% across and 20% down the image with the point 30% across and 20% down the box. So 0% aligns left edges, 100% aligns right edges, and 50% centers. This is the same model as backgrounds, and it means percentages never push the image out of the box with cover.
A Practical Example: Keeping Faces in Frame
Center cropping works for landscapes, but it regularly cuts off the tops of heads in portraits. For team photos, bias the crop upward:
.team-card img {
width: 100%;
aspect-ratio: 1;
object-fit: cover;
object-position: 50% 25%;
}
Faces are usually in the upper third of a portrait, so 25% keeps them visible while still filling the square.
Per-Image Focal Points
If your CMS lets editors choose a focal point, pass it to CSS through a custom property. That keeps the styling in your stylesheet and the data in the markup:
<img
class="hero__img"
src="/images/harbour.jpg"
alt="Fishing boats in the harbour at dawn"
style="--focus-x: 70%; --focus-y: 40%;"
/>
.hero__img {
width: 100%;
height: 60vh;
object-fit: cover;
object-position: var(--focus-x, 50%) var(--focus-y, 50%);
}
The fallback values keep things centered for images that don't have a focal point set.
Real-World Patterns
Uniform Card Grid
This is the pattern I use most. It pairs aspect-ratio for the box with object-fit for the content:
<ul class="card-grid">
<li class="card">
<img
src="/images/recipes/ramen.jpg"
width="1200"
height="1600"
alt="Bowl of miso ramen"
/>
<h3>Weeknight Miso Ramen</h3>
</li>
<li class="card">
<img
src="/images/recipes/tacos.jpg"
width="2000"
height="1000"
alt="Three fish tacos on a plate"
/>
<h3>Crispy Fish Tacos</h3>
</li>
</ul>
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 1.5rem;
list-style: none;
padding: 0;
}
.card img {
display: block;
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
border-radius: 10px;
}
A portrait and a panorama sit side by side at exactly the same size, with neither of them distorted.
Full-Bleed Hero Image
Before object-fit, full-bleed heroes were built with background-image and background-size: cover. That still works, but a real <img> is better: it gets alt text, responsive srcset, lazy loading control, and fetchpriority.
<section class="hero">
<img
class="hero__bg"
src="/images/hero-1600.jpg"
srcset="
/images/hero-800.jpg 800w,
/images/hero-1600.jpg 1600w,
/images/hero-2400.jpg 2400w
"
sizes="100vw"
alt=""
fetchpriority="high"
/>
<div class="hero__content">
<h1>Ship faster with fewer surprises</h1>
</div>
</section>
.hero {
position: relative;
min-height: 70vh;
display: grid;
place-items: center;
overflow: hidden;
}
.hero__bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: -1;
}
.hero__content {
position: relative;
color: #fff;
text-shadow: 0 2px 12px rgb(0 0 0 / 0.5);
}
The empty alt is intentional here, because the image is decorative and the heading carries the meaning. If the image conveys information, describe it.
One thing to watch: z-index: -1 puts the image behind the section's own background. If .hero has a background color, give it isolation: isolate so the negative z-index stays inside the section's stacking context instead of dropping behind the page.
Logo Wall
Partner logos come in every shape, from wide wordmarks to square icons. contain plus a fixed box normalizes them:
.logo-wall {
display: flex;
flex-wrap: wrap;
gap: 2rem;
align-items: center;
}
.logo-wall img {
width: 140px;
height: 60px;
object-fit: scale-down;
}
scale-down avoids blowing up the small logos, and nothing gets cropped.
Video Backgrounds
object-fit works the same way on <video>, which is handy for background loops:
<div class="video-hero">
<video
class="video-hero__media"
autoplay
muted
loop
playsinline
poster="/images/loop-poster.jpg"
>
<source src="/video/loop.webm" type="video/webm" />
<source src="/video/loop.mp4" type="video/mp4" />
</video>
</div>
.video-hero {
position: relative;
height: 80vh;
overflow: hidden;
}
.video-hero__media {
width: 100%;
height: 100%;
object-fit: cover;
}
The poster image also respects object-fit, so the frame shown before playback is cropped the same way. For accessibility, give users a way to pause autoplaying video, and consider not autoplaying at all when prefers-reduced-motion: reduce is set.
Animating object-position
object-position is animatable, which makes for a subtle "pan" effect on hover:
.pan {
width: 100%;
height: 320px;
object-fit: cover;
object-position: 50% 0%;
transition: object-position 3s ease;
}
.pan:hover,
.pan:focus-visible {
object-position: 50% 100%;
}
@media (prefers-reduced-motion: reduce) {
.pan {
transition: none;
}
}
This works nicely for tall screenshots in a portfolio: hover to scroll through the full page capture.
Common Pitfalls
- Only setting one dimension.
object-fitdoes nothing visible unless the element box has a different ratio from the image. Withwidth: 100%; height: auto, the box already matches the image and there's nothing to fit. Set both dimensions, or useaspect-ratio. - Using it on a div.
object-fitonly affects replaced elements. On a<div>with a background image, usebackground-sizeandbackground-position. - Forgetting display: block. Images are inline by default and leave a small gap below them for text descenders. Set
display: blockinside cards. - Cropping important content.
coverwill happily crop text baked into an image, or someone's face. Useobject-positionor a per-image focal point, or choosecontainfor images where cropping is unacceptable. - Assuming it affects the downloaded file.
object-fitcrops visually only. A 4000px image shown in a 200px box is still a 4000px download. Pair it withsrcsetandsizesso the browser fetches an appropriately sized file.
Browser Support
object-fit and object-position are supported in every current browser, including Chrome, Edge, Firefox, and Safari, and have been for many years. The only browser that never supported them was Internet Explorer, which is long retired. You can use both properties in production without fallbacks.
Conclusion
object-fit decides how an image is sized inside its box, and object-position decides which part stays visible. Remember cover for thumbnails and heroes, contain for anything that can't be cropped, and scale-down for unpredictable logos. Add aspect-ratio to define the box and srcset to keep downloads lean, and you have a complete toolkit for handling whatever images your content team throws at you.
Next time a stretched thumbnail shows up in a design review, you'll know it's a two-line fix.


