CSS image() and image-set() for Responsive Image Values

CSS image functions let you define image values directly in styles, including responsive image choices for different screen densities. They are especially useful for backgrounds and other CSS properties that accept images, where you want the browser to pick the best asset without extra scripting.

Quick answer: Use image-set() when you want the browser to choose between image files such as 1x and 2x versions. Use image() when you want a CSS image value with optional fallback behavior and, in some browsers, more advanced source handling.

Difficulty: Intermediate

You'll understand this better if you know: basic CSS syntax, how background-image works, and the idea of device pixel ratios like 1x and 2x.

1. What Are image() and image-set()?

image() and image-set() are CSS image values. They are not HTML elements and not JavaScript APIs; instead, they are functions you can place inside properties that accept an image, such as background-image, border-image-source, or content in some contexts.

These functions matter because CSS can then select the most appropriate file for the device, reducing blur on high-resolution screens and limiting unnecessary bandwidth on lower-resolution screens.

2. Why CSS Image Functions Matter

Without responsive image values in CSS, you often need one image file for every case or a single oversized file that looks sharp everywhere but wastes bandwidth. CSS image functions help close that gap for style-driven images.

They are useful when the image is part of presentation rather than content, such as a hero background, a decorative card texture, or a logo variant in a stylesheet.

They also keep image selection close to the style rule that uses the image, which makes maintenance easier in systems with multiple themes, breakpoints, or density targets.

3. Basic Syntax or Core Idea

The most common function in practice is image-set(). It accepts several image candidates and lets the browser choose one. Each candidate usually includes a URL and a resolution descriptor.

Minimal image-set() syntax

The pattern is straightforward: list one or more image sources, then annotate each with a density such as 1x or 2x.

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

In this example, the browser can choose the 2x file on dense displays and the 1x file on standard displays. The CSS stays readable and the image choice remains declarative.

Basic image() idea

The image() function is a CSS image value that can be used where the property accepts an image source. Support and exact behavior vary by browser, so it is less commonly used than image-set() in everyday projects.

.panel {
  background-image: image("panel.png");
}

In practice, most developers rely on image-set() for multi-resolution assets and use normal url() values when they do not need selection logic.

4. Step-by-Step Examples

The examples below show how these functions work in common CSS situations. The goal is not just to memorize syntax, but to understand how the browser decides which image to use.

Example 1: A background with 1x and 2x versions

This is the classic use case for image-set(): the same visual asset in two resolutions.

.banner {
  background-image: image-set(
    "banner-small.jpg" 1x,
    "banner-large.jpg" 2x
  );
  background-size: cover;
}

This lets the browser choose a sharper source on high-density screens while keeping lower-density screens efficient.

Example 2: Using image-set() with a CSS fallback

Some teams provide a standard url() first, then override it with image-set() for browsers that support it well.

.logo-area {
  background-image: url("logo.png");
  background-image: image-set(
    url("logo.png") 1x,
    url("[email protected]") 2x
  );
}

The fallback line gives older browsers a basic image, while the second line provides density-aware selection where supported.

Example 3: Choosing between image densities for a card

A card background often needs to look clean at multiple screen densities without changing layout.

.card {
  background-image: image-set(
    "card-texture-1x.png" 1x,
    "card-texture-2x.png" 2x,
    "card-texture-3x.png" 3x
  );
}

Here the browser can select from three assets, which is helpful when the image is important to the visual design.

Example 4: Combining image-set() with media-query-based art direction

image-set() handles density, but it does not replace art direction. If you need a different crop or composition on small screens, combine it with media queries.

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

@media (max-width: 600px) {
  .hero {
    background-image: image-set(
      "hero-tall-1x.jpg" 1x,
      "hero-tall-2x.jpg" 2x
    );
  }
}

This example shows the key distinction: density selection happens inside image-set(), while composition changes happen with media queries.

5. Practical Use Cases

CSS image functions are best for decorative and presentation-driven assets. Common project uses include:

If the image is content, such as a product photo or article illustration, HTML image markup usually gives you more control over accessibility and responsive behavior.

6. Common Mistakes

Most problems with these functions come from confusing density switching with layout switching, or from expecting every browser to support the same syntax exactly the same way.

Mistake 1: Using image-set() for art direction

image-set() chooses between similar assets for different pixel densities. It does not automatically crop, reposition, or redesign an image for different screen sizes.

Problem: This code assumes the browser will pick a more suitable composition for mobile, but all candidates describe the same general visual role rather than a layout-specific crop.

.hero {
  background-image: image-set(
    "hero-desktop.jpg" 1x,
    "hero-mobile.jpg" 2x
  );
}

Fix: Use media queries for different crops or compositions, and keep image-set() for density variations within each layout.

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

@media (max-width: 600px) {
  .hero {
    background-image: image-set(
      "hero-mobile-1x.jpg" 1x,
      "hero-mobile-2x.jpg" 2x
    );
  }
}

The corrected version works because it separates size-based art direction from resolution-based selection.

Mistake 2: Forgetting the resolution descriptor

Every candidate in image-set() needs a usable descriptor in practice. If the browser cannot determine how to compare candidates, selection may fail or behave unexpectedly.

Problem: This example looks close to valid CSS, but the candidates are not annotated with resolution information, so the browser has no clear basis for choosing between them.

.tile {
  background-image: image-set(
    "tile.png",
    "[email protected]"
  );
}

Fix: Add explicit density descriptors such as 1x and 2x.

.tile {
  background-image: image-set(
    "tile.png" 1x,
    "[email protected]" 2x
  );
}

The corrected version works because the browser can compare density values directly.

Mistake 3: Expecting image() support everywhere

image() is not as universally used as url() or image-set(). If you depend on it without fallback planning, some browsers may ignore the declaration.

Problem: This code can appear to work in one browser and then fall back unexpectedly in another browser that does not support the function in the same way.

.frame {
  background-image: image("frame.png");
}

Fix: Provide a standard image value first, then layer in the more advanced value where supported and needed.

.frame {
  background-image: url("frame.png");
  background-image: image-set(
    url("frame.png") 1x,
    url("[email protected]") 2x
  );
}

The corrected version works because it preserves a broadly supported baseline while still offering responsive selection.

7. Best Practices

These practices help you avoid blurry assets, unnecessary downloads, and hard-to-debug browser differences.

Practice 1: Use image-set() for density variants of the same asset

Keep all candidates visually equivalent except for resolution. That makes the browser’s choice predictable and prevents layout surprises.

.badge {
  background-image: image-set(
    "badge.png" 1x,
    "[email protected]" 2x
  );
}

This pattern keeps the CSS simple and ensures the visual design remains consistent across screens.

Practice 2: Keep art direction outside image-set()

If the image should change composition, use media queries or container logic, then use image-set() inside each branch if needed.

@media (min-width: 900px) {
  .cover {
    background-image: image-set(
      "cover-wide-1x.jpg" 1x,
      "cover-wide-2x.jpg" 2x
    );
  }
}

This keeps the purpose of each CSS feature clear and easier to maintain.

Practice 3: Always test on a standard screen and a high-DPI screen

Some issues only appear when the device pixel ratio changes. Test both 1x and 2x environments so you know the selected asset is crisp and appropriately sized.

.thumbnail {
  background-image: image-set(
    "thumb-1x.jpg" 1x,
    "thumb-2x.jpg" 2x
  );
}

Testing on different displays confirms that the browser is choosing the file you expected.

8. Limitations and Edge Cases

CSS image functions are useful, but they have important limits.

A common “not working” report is that the 2x file never appears. That often happens because the device pixel ratio, the browser’s zoom level, or the asset descriptors do not make the 2x candidate the best choice.

9. Practical Mini Project

Let’s build a small card component that uses image-set() for a decorative header background and a sensible fallback image for broader support.

.feature-card {
  padding: 1.5rem;
  border-radius: 1rem;
  color: #fff;
  background-color: #1f2937;
  background-image: url("card-bg-1x.jpg");
  background-image: image-set(
    url("card-bg-1x.jpg") 1x,
    url("card-bg-2x.jpg") 2x
  );
  background-size: cover;
  background-position: center;
}

.feature-card h3 {
  margin-top: 0;
}

This small pattern is common in production: one reliable fallback declaration, then a responsive image value that improves rendering on high-density screens.

10. Key Points

11. Practice Exercise

Expected output: A hero background that looks crisp on retina displays and uses a different composition on narrow screens.

Hint: Use media queries for the crop change, and image-set() inside each layout branch for density switching.

.hero {
  min-height: 20rem;
  background-size: cover;
  background-position: center;
  background-image: url("hero-desktop-1x.jpg");
  background-image: image-set(
    url("hero-desktop-1x.jpg") 1x,
    url("hero-desktop-2x.jpg") 2x
  );
}

@media (max-width: 600px) {
  .hero {
    background-position: top center;
    background-image: image-set(
      url("hero-mobile-1x.jpg") 1x,
      url("hero-mobile-2x.jpg") 2x
    );
  }
}

12. Final Summary

image-set() gives CSS a clean way to choose between multiple versions of the same image, which is most useful for backgrounds and other decorative assets. It helps you serve a sharper file on dense displays without forcing every user to download the largest version.

image() is part of the same family of CSS image values, but it is less central in everyday responsive workflows. In practice, most developers use url() for simple cases and image-set() when they need density-aware selection.

For the best results, separate density switching from art direction, keep a fallback in mind, and test on more than one display type. If you want to go further, the next useful topic is how CSS background images interact with background-size, background-position, and media queries.