Styling Images

CSS · Lesson 39 of 55 8 min read Updated

Images sell products, introduce people and make articles worth reading. But an image that stretches, overflows a phone screen, or jumps the page around as it loads makes the whole site feel broken. A handful of CSS properties fix all of these problems and add a polished look on top.

In this lesson you will make images responsive, crop them neatly into any shape of box, create circular avatars, style captions and loading placeholders, and build smooth hover effects. The HTML side, such as alt text, srcset and <picture>, is covered in HTML Images and Responsive Images.

The responsive baseline

Start every project with this rule:

CSS
img {
  max-width: 100%;   /* never wider than the container */
  height: auto;      /* keep the original proportions */
  display: block;    /* remove the small gap below inline images */
}

Keep the width and height attributes in your HTML, such as width="800" height="500". The browser uses them to reserve space with the correct proportions before the file arrives, so the text below does not jump. height: auto is important here: without it, the height attribute would fix the image at 500 pixels tall even after max-width shrank its width, and the picture would look squashed or letterboxed.

For CSS background images, image-set() lets the browser choose a sharper file on high-resolution screens:

CSS
.hero {
  background-image: image-set("hero-1x.webp" 1x, "hero-2x.webp" 2x);
}

Cropping with object-fit and object-position

You met object-fit in Sizing, Aspect Ratio & Overflow. It decides how an image fills a box that has a different shape from the picture:

ValueResult
fillDefault. Stretches to fill the box and distorts the picture
coverFills the whole box and crops the edges
containShows the whole picture, leaving empty bands
scale-downLike contain, but never enlarges a small image
noneKeeps the original size and crops whatever does not fit

object-position picks which part stays visible when the image is cropped. It works like background-position: center is the default, and you can use keywords such as top or percentages such as 50% 20% (centred across, near the top).

Combined with aspect-ratio, this turns photos of every shape into a tidy, matching grid:

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Uniform thumbnails</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 1rem; }
    .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); gap: 0.75rem; max-width: 40rem; }
    .grid figure { margin: 0; }
    .grid img {
      width: 100%;
      height: auto;
      aspect-ratio: 4 / 3;     /* every thumbnail has the same shape */
      object-fit: cover;       /* crop instead of stretching */
      border-radius: 8px;
      background: #e2e8f0;     /* placeholder colour while loading */
    }
    .grid .top { object-position: 50% 15%; }   /* keep the top of the tower in view */
    figcaption { font-size: 0.85rem; margin-top: 0.3rem; color: #475569; }
  </style>
</head>
<body>
  <div class="grid">
    <figure><img src="https://course.ethicscomputer.in/assets/samples/landscape.svg" alt="Hills and a river at sunrise" width="800" height="500"><figcaption>Landscape, cover</figcaption></figure>
    <figure><img src="https://course.ethicscomputer.in/assets/samples/portrait.svg" alt="Tall brick clock tower" width="400" height="600"><figcaption>Portrait, cover (centre)</figcaption></figure>
    <figure><img class="top" src="https://course.ethicscomputer.in/assets/samples/portrait.svg" alt="Tall brick clock tower" width="400" height="600"><figcaption>Portrait, 50% 15%</figcaption></figure>
  </div>
</body>
</html>

The tall clock tower and the wide landscape now share the same 4:3 frame. The middle thumbnail shows the centre of the tower, which cuts off the clock. The last one uses object-position: 50% 15% so the clock face and roof stay in view. Change the ratio to 1 to make square thumbnails.

Rounded and circular images

border-radius rounds an image's corners just like any box. A value of 50% makes a circle, but only if the image is square. On a rectangle, 50% gives an oval. The safe recipe for avatars sets the shape first and then crops:

CSS
.avatar {
  width: 4rem;
  height: auto;
  aspect-ratio: 1;          /* force a square */
  object-fit: cover;        /* crop any photo to fill it */
  border-radius: 50%;       /* then round it into a circle */
}

To add a ring around an avatar without changing its size, use outline with outline-offset, or a border in the page background colour when avatars overlap each other.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Avatars</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 1.5rem; background: #f8fafc; }
    .avatar { width: 5rem; height: auto; aspect-ratio: 1; object-fit: cover; border-radius: 50%; display: block; }
    .row { display: flex; gap: 1.5rem; align-items: center; margin-bottom: 2rem; }
    .ring { outline: 3px solid #16a34a; outline-offset: 3px; }   /* "online" ring */
    .oval { width: 8rem; height: 5rem; border-radius: 50%; object-fit: cover; }
    .group { display: flex; }
    .group img { width: 3rem; border: 3px solid #f8fafc; }        /* border matches the page */
    .group img + img { margin-left: -0.9rem; }                     /* overlap */
  </style>
</head>
<body>
  <div class="row">
    <img class="avatar" src="https://course.ethicscomputer.in/assets/samples/avatar.svg" alt="Cartoon avatar of a smiling person" width="200" height="200">
    <img class="avatar ring" src="https://course.ethicscomputer.in/assets/samples/avatar.svg" alt="Cartoon avatar of a smiling person, shown as online" width="200" height="200">
    <img class="oval" src="https://course.ethicscomputer.in/assets/samples/landscape.svg" alt="Hills and a river at sunrise" width="800" height="500">
  </div>
  <div class="group" role="group" aria-label="Members of the Pune cycling club">
    <img class="avatar" src="https://course.ethicscomputer.in/assets/samples/avatar.svg" alt="Cartoon avatar of a smiling person" width="200" height="200">
    <img class="avatar" src="https://course.ethicscomputer.in/assets/samples/avatar.svg" alt="Cartoon avatar of a smiling person" width="200" height="200">
    <img class="avatar" src="https://course.ethicscomputer.in/assets/samples/avatar.svg" alt="Cartoon avatar of a smiling person" width="200" height="200">
  </div>
</body>
</html>

The ring sits 3 pixels away from the circle and never changes the layout. The landscape with border-radius: 50% becomes an oval, because its box is not square. In the group, each avatar slides under the next one, and the border in the page colour draws a clean gap between them.

Captions, placeholders and broken images

A few small touches make images feel finished:

CSS
figure { margin: 0; }
figcaption {
  font-size: 0.875rem;
  color: #475569;
  padding-block-start: 0.4rem;
}
img {
  background-color: #e2e8f0;   /* grey box until the image loads */
  font-style: italic;          /* styles the alt text if the image fails */
}
.pixel-art {
  image-rendering: pixelated;  /* sharp squares when small images are scaled up */
}

The background colour shows while the file downloads, and behind any transparent parts of PNG or SVG images, so pick a light neutral colour. If an image fails to load, many browsers show its alt text inside the box, and that text uses the image's font styles. image-rendering: pixelated keeps hard edges when you enlarge pixel art or a QR code, instead of blurring them.

Hover effects

A popular effect zooms the photo slightly when you point at a card, while the card itself stays the same size. The trick is a frame with overflow: hidden around the image, so the bigger picture is cropped by the frame.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Image hover</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 1.5rem; }
    .tour { display: block; position: relative; max-width: 20rem; border-radius: 12px; overflow: hidden; color: white; text-decoration: none; }
    .tour img { width: 100%; height: auto; display: block; transition: scale 0.4s ease, filter 0.4s ease; }
    .tour .caption {
      position: absolute;
      inset: auto 0 0 0;
      padding: 2rem 1rem 0.8rem;
      background: linear-gradient(transparent, rgb(0 0 0 / 70%));
      font-weight: 600;
    }
    .tour:hover img, .tour:focus-visible img { scale: 1.08; filter: saturate(1.3); }
    .tour:focus-visible { outline: 3px solid #f59e0b; outline-offset: 3px; }
    @media (prefers-reduced-motion: reduce) {
      .tour img { transition: none; }
    }
  </style>
</head>
<body>
  <a class="tour" href="#munnar">
    <img src="https://course.ethicscomputer.in/assets/samples/landscape.svg" alt="Green hills and a river at sunrise in Munnar" width="800" height="500">
    <span class="caption">Munnar sunrise trek, ₹2,499</span>
  </a>
</body>
</html>

Point at the card, or press Tab to focus it: the photo grows by 8% and gets a little more colourful, but the rounded frame stays fixed, because overflow: hidden crops the extra. The caption sits on a dark gradient so the white text stays readable over any part of the photo. Hover effects also apply on keyboard focus with :focus-visible, and people who ask their system for reduced motion get an instant change instead of an animation.

For more effects, filter can turn photos grey, blur them or adjust brightness (see Filters & Blend Modes), and clip-path can cut images into circles, hexagons and other shapes (see Clip-path, Shapes & Masks).

Common mistakes

  • max-width: 100% without height: auto. The height attribute keeps the old height and the image looks squashed.
  • border-radius: 50% on a rectangle. You get an oval. Add aspect-ratio: 1 and object-fit: cover first.
  • Stretched thumbnails. Use object-fit: cover instead of letting the default fill distort pictures.
  • Zoom effects that spill out of the card. Put overflow: hidden on the frame so the bigger picture is cropped.
  • Hover-only effects. Repeat them for :focus-visible, and respect prefers-reduced-motion.
  • Text on photos without a backdrop. Add a gradient or solid layer behind captions.

Key points

  • img { max-width: 100%; height: auto; display: block; } is the responsive baseline; keep width and height attributes in HTML.
  • object-fit: cover crops an image to fill its box, and object-position chooses the visible part.
  • aspect-ratio gives every image the same shape, whatever the original file.
  • Circular avatars need a square box: aspect-ratio: 1, object-fit: cover, border-radius: 50%.
  • A light background-color acts as a loading placeholder, and image-rendering: pixelated keeps pixel art sharp.
  • Hover zoom needs a frame with overflow: hidden; also support :focus-visible and reduced motion.

Exercise

Build a "Meet the team" section for a Jaipur travel agency with three people. Each person has the avatar sample image as a circular 6rem photo with a coloured ring, their name and role below. Above the team, add a holiday package card using the landscape image in a 16:9 frame with object-fit: cover, rounded corners, a caption on a dark gradient and a gentle zoom on hover and keyboard focus.

Show solution
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Meet the team</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 1.5rem; }
    .package { display: block; position: relative; max-width: 24rem; border-radius: 14px; overflow: hidden; color: white; text-decoration: none; margin-bottom: 2rem; }
    .package img { width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; display: block; transition: scale 0.4s; }
    .package span { position: absolute; inset: auto 0 0; padding: 2rem 1rem 0.75rem; background: linear-gradient(transparent, rgb(0 0 0 / 70%)); }
    .package:hover img, .package:focus-visible img { scale: 1.06; }
    @media (prefers-reduced-motion: reduce) { .package img { transition: none; } }
    .team { display: flex; flex-wrap: wrap; gap: 2rem; }
    .team figure { margin: 0; text-align: center; }
    .team img { width: 6rem; height: auto; aspect-ratio: 1; object-fit: cover; border-radius: 50%; outline: 3px solid #e11d48; outline-offset: 3px; }
    .team figcaption { margin-top: 0.6rem; }
    .team small { display: block; color: #64748b; }
  </style>
</head>
<body>
  <a class="package" href="#aravalli">
    <img src="https://course.ethicscomputer.in/assets/samples/landscape.svg" alt="Hills and a river at sunrise" width="800" height="500">
    <span>Aravalli hills and lakes, 5 days, ₹18,999</span>
  </a>
  <div class="team">
    <figure><img src="https://course.ethicscomputer.in/assets/samples/avatar.svg" alt="Cartoon avatar of a smiling person" width="200" height="200"><figcaption>Rohan Mehta<small>Tour manager</small></figcaption></figure>
    <figure><img src="https://course.ethicscomputer.in/assets/samples/avatar.svg" alt="Cartoon avatar of a smiling person" width="200" height="200"><figcaption>Ananya Singh<small>Bookings</small></figcaption></figure>
    <figure><img src="https://course.ethicscomputer.in/assets/samples/avatar.svg" alt="Cartoon avatar of a smiling person" width="200" height="200"><figcaption>Kabir Rathore<small>Guide</small></figcaption></figure>
  </div>
</body>
</html>

The package image is cropped to 16:9 and only the picture zooms, because the link is the frame with overflow: hidden. Each avatar is forced square before border-radius: 50% turns it into a circle, and the outline ring does not change its size.

Quick Quiz

1. Why add height: auto next to max-width: 100% on images?

2. An image is 8rem wide and 5rem tall with border-radius: 50%. What shape is it?

3. Which property chooses which part of a cropped image stays visible?

4. On hover, a zoomed photo spills outside its rounded card. What is missing?

Learn CSS with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call