agentleFS
shipiitshipiit/shipit-ui-design/skills/images-and-media/SKILL.md

images-and-media

Skill0 starsChanged 6 months agoMITView on GitHub

Apply senior image and media design — when to use raster vs SVG vs CSS gradients, aspect ratios, Next.js Image / lazy loading / blur placeholder strategies, art direction across breakpoints, photo treatment (duotone, mask, overlay), avatar systems. Auto-activates on edits to files containing `<img>`, `next/image`, `<picture>`, or media-heavy components.

The file

We split this skill into the rules it gives an agent

6 rules. About 0 are common, 1 is distinctive, and 5 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Never raster for icons. Never SVG for photographs.

    line 38
  2. Very specific:

    Always set explicit dimensions or aspect-ratio so the browser reserves layout before the image loads.

    line 44
  3. Very specific:

    Always pair with width + height attributes when using raw <img>.

    line 60
  4. Very specific:

    Never loading="lazy" on the LCP image — it delays the largest contentful paint and tanks Core Web Vitals.

    line 72
  5. Very specific:

    Use <source media="…"> for crop swaps.

    line 107
  6. Very specific:

    Never both aria-hidden="true" and alt="some text" on the same element.

    line 156

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Images and Media

Plain <img src> is the lowest grade of image work. Senior output reasons about: format choice (raster vs vector vs gradient), aspect ratio reservation (no CLS), loading strategy (eager LCP / lazy below-fold), placeholder strategy (blur), art direction (different crops per breakpoint), and post-treatment (duotone, overlays). This skill encodes those defaults.

Constitution (verbatim — applies to every artifact)

  1. Max 300 lines per file. If a generated component would exceed, split before writing.
  2. No hardcoded design values. Colors, spacing, radii, shadows, durations, easings — all from tokens.
  3. Every interactive element has hover, active, focus-visible, and disabled states.
  4. All motion respects prefers-reduced-motion.
  5. Every image / illustration has alt text or aria-hidden if decorative.
  6. Dark mode is never an afterthought — emitted alongside light from the start.
  7. Stack-respect: never introduce a new framework or styling system; adapt to what's there.

When to use what — decision tree

NeedFormatWhy
Brand mark / logoSVGCrisp at any size, themeable, tiny payload
IconSVGSame; never raster for icons
Data visualizationSVG (or <canvas> for high cardinality)Vector, accessible, themeable
Product photographyRaster (AVIF → WebP → JPEG fallback)Photographic content needs photographic compression
Editorial photographyRasterSame
Marketing illustrationSVG (preferred) or rasterSVG when geometric/two-tone; raster when full-painted
Hero ambient decorationCSS gradient or SVG meshLighter than a raster image; themeable
Decorative patternSVG <pattern>Tiles cheaply, scales
User-generated content (avatars, uploads)Raster (AVIF/WebP)Source is raster
Animated illustrationLottie (JSON) or SVG + CSS/JSGIF is never the answer

Never raster for icons. Never SVG for photographs. Never animated GIF — Lottie or video instead.


Aspect ratios — reserve space, prevent CLS

Always set explicit dimensions or aspect-ratio so the browser reserves layout before the image loads.

RatioUse
16:9Hero banners, video thumbnails, blog headers
4:3Product photography, editorial
1:1Social tiles, avatars, gallery thumbnails
21:9Cinematic hero bands, full-bleed marketing
3:4Mobile portrait hero, app-screenshot frames
9:16Reels / stories embeds
.thumb { aspect-ratio: 16 / 9; width: 100%; }
.avatar { aspect-ratio: 1; width: var(--space-10); }

Always pair with width + height attributes when using raw <img>; the browser uses them to compute the intrinsic ratio and reserve space before bytes arrive.


Loading strategy

PositionAttributeNotes
Above the fold (LCP candidate)loading="eager" + fetchpriority="high"One per page max
Below the foldloading="lazy"Default for everything else
Decorative bgCSS background-imageNo loading attribute applies; use image-set() for retina

Never loading="lazy" on the LCP image — it delays the largest contentful paint and tanks Core Web Vitals.


Blur placeholder

Reserve layout, hint at content while bytes arrive. Three approaches:

  • Next.js next/image — placeholder="blur" plus blurDataURL (auto-generated for static imports, manual base64 for remote). Cleanest in Next stacks.
  • Hand-rolled — encode a 32×18 (or matching-ratio) low-quality JPEG, base64-embed, set as background-image, layer the real image on top with opacity transition.
  • LQIP via CSS color — sample the dominant color, set as a solid background. Cheapest, no payload, works anywhere.
<div style={{ aspectRatio: "16/9", backgroundImage: `url(${blurDataURL})`,
  backgroundSize: "cover", backgroundColor: "var(--color-surface)" }}>
  <img src={src} alt={alt} loading="lazy" decoding="async"
    style={{ width: "100%", height: "100%", objectFit: "cover" }} />
</div>

Art direction — different crops per breakpoint

Mobile gets a tighter crop than desktop. The same source rarely composes well at both 21:9 and 3:4.

<picture>
  <source media="(min-width: 1024px)" srcset="/hero-wide.avif" type="image/avif" />
  <source media="(min-width: 1024px)" srcset="/hero-wide.webp" type="image/webp" />
  <source media="(max-width: 1023px)" srcset="/hero-portrait.avif" type="image/avif" />
  <img src="/hero-wide.jpg" alt="…" width="1600" height="900" />
</picture>

Use <source media="…"> for crop swaps; use srcset (within a single <source>) only for resolution swaps of the same crop.


Photo treatment — never raw stock

Untreated stock photography looks like stock photography. Apply at least one treatment:

  • Duotone — blend the photo with two brand-tinted versions (highlights = --color-brand-200, shadows = --color-brand-800). mix-blend-mode: multiply plus filter: grayscale(1) contrast(1.1).
  • Gradient overlay for legibility — 40–60% opacity from --color-bg (top-down or bottom-up depending on copy placement). Required when text sits on the image.
  • Clip-path masks — non-rectangular crops for editorial feel. Always pair with a fallback border-radius on browsers that don't support the path.
  • Parallax depth — image translates at 0.6× scroll speed inside a fixed-height container. Reduced-motion: static.
.duotone { filter: grayscale(1) contrast(1.1); }
.duotone::after { content: ""; position: absolute; inset: 0; mix-blend-mode: multiply;
  background: linear-gradient(135deg, var(--color-brand-300), var(--color-brand-700)); }
.legible-overlay::after { content: ""; position: absolute; inset: 0;
  background: linear-gradient(to top, var(--color-bg) 0%, transparent 60%); }

Avatar systems

Sizes (token-mapped):

SizeDiameterUse
xs24 px (--space-6)Inline mention, comment list density
sm32 px (--space-8)Compact list rows
md40 px (--space-10)Default list, navbar
lg48 px (--space-12)Profile cards
xl64 px (--space-16)Profile header

Letter fallback: initials in --color-brand-100 background, --color-brand-700 foreground, font-weight: 600. Generate from the user's name; first letter of first + first letter of last, uppercased.

Status dot (online/away/offline): bottom-right, 25% of avatar diameter, box-shadow: 0 0 0 2px var(--color-bg) so the ring matches the surrounding surface and reads as a cutout.


Decoration vs content — accessibility

Constitution rule 5: every image is labeled or marked decorative.

  • Decoration (purely visual, content is communicated elsewhere) — <img alt="" /> or <svg aria-hidden="true"> or role="presentation". Never describe a decorative image.
  • Content (image conveys meaning, no equivalent text on the page) — alt="…" describes function, not appearance. "Quarterly revenue chart, Q3 highest", not "blue line graph."
  • Functional image (linked or in a button) — alt describes the destination/action. "Search", not "magnifying glass."

Never both aria-hidden="true" and alt="some text" on the same element. Empty alt="" is correct for decorative; missing alt is a violation.


Library decisions — verify at build time

Selection criteria, two to three options. Pick based on framework and deployment:

  • next/image — Next.js stacks. Automatic AVIF/WebP, srcset, lazy, blur placeholder, layout reservation. Default for Next projects.
  • Native <img> with srcset + <picture> — vanilla, framework-neutral, no build-time optimization. Pair with a build step (Sharp, ImageMagick, squoosh-cli) to emit AVIF/WebP variants.
  • astro:assets — Astro projects. Build-time optimization, similar API to next/image.
  • enhanced:img — SvelteKit projects. Same shape.
  • <v-img> / Nuxt Image — Vue/Nuxt stacks.

Selection criteria:

IfUse
Project is Next.jsnext/image
Project is Astroastro:assets
Project is Nuxt or SvelteKitthe framework's image component
Project is vanilla / Vite / CRA<picture> + srcset + a build-time optimizer
Image is an SVGinline as JSX (themeable) or <img src> (cacheable)
Image is user-generated / hot-loadeda CDN image transformer (Cloudinary, imgix, Vercel)

Stack-respect (constitution rule 7): never add a new image library if one is already conventional in the framework. Verify at build time before recommending.


Anti-patterns

  • <img> without width/height or aspect-ratio — guaranteed CLS.
  • loading="lazy" on the LCP image — tanks Core Web Vitals.
  • Raster icons — pixelate on retina, can't theme.
  • Animated GIFs — replace with <video autoplay muted loop playsinline> or Lottie.
  • background-image: url(big.jpg) for hero — bypasses lazy loading and srcset.
  • Raw stock photography with no treatment — looks like every other landing page.
  • Avatar fallback with random colors — accessibility nightmare; use one tokened brand bg.
  • alt="image of …" — screen readers already announce "image"; redundant.

Cross-references

  • svg-illustration/SKILL.md — when image is SVG (icons, illustrations, mockups).
  • ui-design-principles/SKILL.md — accessibility rubric (rule 5, alt text), spacing rhythm for media-heavy layouts.
  • rich-ui-patterns/SKILL.md — when media sits inside a browser/device mockup or behind a glass overlay.
  • references/canonical-tokens.md — token names referenced for avatar sizing and duotone tints.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.