Responsive Images skill for Codex

The right bytes for every screen. Codex writes image markup from the real CSS layout: it measures rendered widths, derives a sizes value that matches each breakpoint, builds a sensible width ladder, wraps photos in picture with AVIF and WebP sources, reserves space with width and height, and gives the LCP image high fetch priority instead of lazy loading. It then checks currentSrc at several viewport widths and pixel densities.

  • Frontend
  • $responsive-images
  • 144 lines
  • 2 files
  • ~1,800 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- responsive-images

Installs to ~/.agents/skills/responsive-images/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $responsive-images, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Writes correct responsive image markup - srcset with width descriptors, accurate sizes attributes, picture with AVIF and WebP sources, art direction with media queries, width and height for zero layout shift, fetchpriority for the LCP image and lazy loading below the fold - plus framework components such as next/image, Astro Image and Nuxt Image. Use when images are blurry or oversized on mobile, the LCP element is an image, Lighthouse flags properly size images or next-gen formats, a hero needs a different crop on phones, or someone asks what sizes value to use.

Use it when

  • Images download larger than they render, especially on mobile
  • Lighthouse flags image sizing, formats or a lazily loaded LCP image
  • A banner needs a different crop at small widths
  • next/image, Astro Image or Nuxt Image output looks blurry or oversized

Use something else when

How to check the result

With cache disabled, check the downloaded candidate and currentSrc at 375, 768 and 1440 px for DPR 1 and 2, then confirm Lighthouse reports no image sizing or lazy LCP warnings.

The mistake it prevents

Omitting sizes so the browser assumes 100vw, or lazy-loading the LCP hero image.

~/.agents/skills/responsive-images/SKILL.md

--- name: responsive-images description: Writes correct responsive image markup - srcset with width descriptors, accurate sizes attributes, picture with AVIF and WebP sources, art direction… license: MIT ---

Done well means: the browser downloads the smallest file that looks sharp at the rendered size and density, the LCP image starts loading at high priority from the first HTML response, no image shifts layout, and modern formats are served with a safe fallback.

Before changing anything

  1. Read AGENTS.md; find how images are produced (static files, build plugin, image CDN like Cloudinary/imgix/Cloudflare, or a framework component). Generate variants with that pipeline, not by hand.
  2. Identify the LCP element for key templates (Lighthouse or DevTools Performance panel).
  3. Measure actual rendered widths at a few viewport widths (DevTools: hover the img, note rendered vs intrinsic size). sizes must come from the CSS layout, not guesses.

Pick the markup

SituationMarkup
Same image, layout width varies with viewportimg + srcset (w descriptors) + sizes
Fixed CSS size (avatar, logo, icon)img + srcset with 1x, 2x density descriptors
Modern formats with fallbackpicture + source type="image/avif" + type="image/webp" + img
Different crop or composition per breakpointpicture + source media="…" (art direction)
Vector artwork, logosSVG, no srcset
Decorative backgroundCSS image-set(); never for content or LCP if avoidable

Fluid image

<img
  src="/img/team-800.jpg"
  srcset="/img/team-400.jpg 400w, /img/team-800.jpg 800w,
          /img/team-1200.jpg 1200w, /img/team-1600.jpg 1600w, /img/team-2400.jpg 2400w"
  sizes="(min-width: 1280px) 600px, (min-width: 768px) 50vw, calc(100vw - 32px)"
  width="1600" height="1067"
  alt="Five engineers reviewing a circuit board at a workbench"
  loading="lazy" decoding="async">

How to write sizes:

  • Describe the image’s rendered CSS width at each breakpoint, in the same order the browser evaluates media conditions: first match wins, so list larger breakpoints first.
  • Account for padding and gaps (calc(100vw - 32px), calc((100vw - 96px) / 3)).
  • Cap at the container max width (600px above 1280px), or large screens download 2400w.
  • Without sizes, the browser assumes 100vw and over-downloads for anything narrower.
  • For lazy images only, sizes="auto" (with fallback, e.g. sizes="auto, 50vw") lets supporting browsers use the laid-out width. It is ignored for eager images.

Width ladder: roughly every 300–500 px of width from the smallest rendered size to rendered max x 2 (cap at 2x density; 3x adds bytes with little visible gain). Five or six candidates is usually enough.

Formats

<picture>
  <source type="image/avif" srcset="/img/hero-800.avif 800w, /img/hero-1600.avif 1600w" sizes="100vw">
  <source type="image/webp" srcset="/img/hero-800.webp 800w, /img/hero-1600.webp 1600w" sizes="100vw">
  <img src="/img/hero-1600.jpg" srcset="/img/hero-800.jpg 800w, /img/hero-1600.jpg 1600w"
       sizes="100vw" width="1600" height="900" alt="" fetchpriority="high">
</picture>
FormatUse forNotes
AVIFPhotos, gradientsSmallest at equal quality; slower to encode; start around quality 50–60
WebPFallback for AVIF, or default where encoding time mattersQuality ~75
JPEG (progressive)Final fallbackmozjpeg quality ~75–80
PNGOnly when lossless with transparency is requiredTry lossless WebP first
SVGLogos, icons, illustrationsOptimize with SVGO; sanitize user-uploaded SVG

Order source elements best-first; the browser takes the first type it supports. sizes must be repeated on every source and the img.

Art direction

<picture>
  <source media="(max-width: 767px)" srcset="/img/hero-portrait-600.avif 600w, /img/hero-portrait-1200.avif 1200w" type="image/avif" sizes="100vw" width="1200" height="1500">
  <source media="(max-width: 767px)" srcset="/img/hero-portrait-600.jpg 600w, /img/hero-portrait-1200.jpg 1200w" sizes="100vw" width="1200" height="1500">
  <img src="/img/hero-wide-1600.jpg" srcset="/img/hero-wide-1600.jpg 1600w, /img/hero-wide-2400.jpg 2400w" sizes="100vw" width="2400" height="1000" alt="Ferry crossing the harbour at dusk">
</picture>

width/height on source lets each crop reserve its own aspect ratio. Use art direction only when the crop actually changes; for scaling alone, use srcset.

Loading priority

ImageAttributes
LCP image (hero, product shot)fetchpriority="high", no loading="lazy", in the initial HTML (not injected by JS, not a CSS background)
Above the fold, not LCPdefault (eager), no fetchpriority
Below the foldloading="lazy" decoding="async"
Carousel slides after the firstloading="lazy"

If the LCP image is discovered late (CSS background, JS-rendered), add <link rel="preload" as="image" imagesrcset="…" imagesizes="…" fetchpriority="high"> in the head with the same srcset and sizes; mismatched values download twice.

Framework components

  • next/image: pass sizes whenever the image is not fixed width (otherwise it generates a 1x/2x set for the width prop, or assumes 100vw with fill). Use priority (or preload/fetchPriority in newer versions; check the installed Next.js docs) on the LCP image. Configure images.remotePatterns for remote hosts and formats: ['image/avif', 'image/webp'].
  • Astro <Image> / <Picture> from astro:assets: set widths and sizes, formats={['avif','webp']} on Picture, loading="eager" + fetchpriority="high" for LCP.
  • Nuxt Image: <NuxtImg sizes="sm:100vw md:50vw lg:600px">, <NuxtPicture> for formats.
  • Image CDNs: generate srcset from URL parameters (?w=800&fm=avif&q=60), and keep a helper that builds the whole attribute so widths stay consistent.

Gotchas

  • Missing width/height (or CSS aspect-ratio) causes CLS. The attributes set the ratio only; CSS width: 100%; height: auto still controls size.
  • loading="lazy" on the LCP image delays it by hundreds of ms; the most common LCP regression.
  • alt="" for decorative images only. Informative images need alt describing the content and purpose (WCAG 1.1.1); do not start with “image of”.
  • Density descriptors (2x) and width descriptors (800w) cannot be mixed in one srcset.
  • Browsers may pick a larger cached candidate; testing with a warm cache misleads. Disable cache.
  • Upscaled sources: never generate candidates wider than the original master.

Verification

  • DevTools Network (cache disabled), at 375, 768 and 1440 px widths with DPR 1 and 2: the downloaded candidate is the smallest one at least as wide as rendered width x DPR.
  • Elements panel: currentSrc of each image matches expectation.
  • Lighthouse: no “Properly size images”, “Serve images in next-gen formats”, or “Largest Contentful Paint image was lazily loaded” findings.
  • CLS from images is 0 in the Performance panel layout-shift track.
  • Format fallback: disable AVIF in a test browser or check in an older Safari; JPEG loads.

Output

For each changed image: markup chosen, candidate widths and formats, the sizes value with the layout reasoning behind it, loading priority, and before/after transferred bytes at one mobile and one desktop width.

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Responsive Images"
  short_description: "The right bytes for every screen"
  default_prompt: "$responsive-images Our hero image is the LCP element and loads slowly, fix the markup"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem