Image Opt Widget

A compact panel that visualises an image pipeline before and after a format swap. Each entry renders as a row — its label, a bar sized to the encoded payload, the size chip on the right, and pixel dimensions trailing on wide layouts. A footer prints the cumulative initial payload against the baseline so the headline savings read at a glance.

Preview

Image pipeline

Four assets — slide to compare format and quality tradeoffs.

  • hero175 KB
  • card-168 KB
  • card-279 KB
  • banner63 KB
Initial payloadwas 538 KB
175 KB29%
WebP at quality 75. Total transferred 385 KB.
Customize
Encoder
webp
75%
Display

Installation

npx shadcn@latest add https://craftbits.dev/r/image-opt-widget.json

Usage

import { ImageOptWidget } from "@craft-bits/core";
 
<ImageOptWidget
  title="Image pipeline"
  format="webp"
  quality={75}
  images={[
    { id: "hero", label: "hero", bytes: 245 * 1024, dimensions: { w: 1600, h: 900 } },
    { id: "card", label: "card", bytes: 95 * 1024, dimensions: { w: 640, h: 480 }, lazy: true },
  ]}
/>

Set showOriginal to render the rail at the baseline payload — useful when you want a static "before" snapshot beside the optimised view:

<ImageOptWidget showOriginal images={images} />

Anatomy

  • Header. Optional title (renders with the cb-label style) and a description sub-line. Omit both for a chromeless panel.
  • Row. One <li> per image. The label sits left, the bar fills the middle, the size chip sits on the right, and the pixel dimensions plus an optional lazy badge trail on wide layouts.
  • Bar. A track sized to the largest original payload; each bar's width scales to the displayed payload (optimised by default, baseline when showOriginal is set).
  • Footer. Renders the initial payload total against the baseline. A negative-percent chip reports the headline savings.

Understanding the component

  1. Format ratios. Each target format has a baseline compression ratio against JPEG quality 100 — jpeg: 1, webp: 0.65, avif: 0.5. Override formatRatios to plug in your own audit numbers.
  2. Quality factor. The widget scales the format ratio by 0.5 + (quality / 100) * 0.8, so quality 100 lands at the headline ratio and quality 1 lands at half. The mapping is intentionally rough — it's a teaching estimate, not an encoder simulator.
  3. Lazy entries. Entries with lazy: true still count towards the total transferred payload but drop out of the initial payload total in the footer. This mirrors a loading="lazy" image below the fold.
  4. Verdicts. A row paints good when the format swap shaved at least 30% off the original, neutral for smaller wins, and bad only while showOriginal is on. The footer paints good once the initial-payload savings clear 30%, neutral at 10%, and bad below.
  5. Motion. Bars animate from zero on the first paint, the size cell re-animates whenever the value changes, and all animations short-circuit under prefers-reduced-motion.

Props

PropTypeDefaultDescription
imagesImageOptWidgetImage[]requiredOrdered list of image entries.
titleReactNodeOptional heading above the rail.
descriptionReactNodeOptional sub-headline under the title.
format'jpeg' | 'webp' | 'avif''webp'Target encoded format.
qualitynumber75Encoder quality, 1 to 100.
formatRatiosPartial<Record<format, number>>Override the baseline compression ratios.
hideTotalbooleanfalseHide the initial-payload footer.
showOriginalbooleanfalseRender bars at the baseline size.
headingAs'h2' | 'h3' | 'h4''h3'Tag for the title element.
classNamestringMerged onto the root via cn().

ImageOptWidgetImage

FieldTypeDescription
idstringStable identifier.
labelReactNodeVisible row label. Falls back to id.
format'jpeg' | 'webp' | 'avif' | 'png' | 'gif'Original format. Informational.
bytesnumberOriginal encoded payload in bytes.
dimensions{ w: number; h: number }Pixel dimensions of the asset.
lazybooleanDrop the asset from the initial-payload total.

Accessibility

  • The wrapper is a <section> with data-cb-edu="image-opt-widget" and data-cb-verdict mirroring the cumulative verdict. Rows form a role="list" of role="listitem" entries.
  • The size column carries aria-live="polite" so updates are announced without interrupting the user.
  • Each row exposes data-cb-verdict="good" | "neutral" | "bad" and data-cb-lazy so consumers can extend tone-specific styling without monkey-patching CSS.
  • The dimensions cell carries a descriptive aria-label so the figure reads aloud as "1600 by 900 pixels" rather than as the visual punctuation.
  • The active format and the total transferred payload are announced via a visually-hidden footer so screen-reader users get the same headline summary.
  • Animations short-circuit under prefers-reduced-motion.

Credits

  • Extracted from: terminal-dreams (src/components/frontend-design/sdp-web-performance/ui/ImageOptWidget.tsx). The original pulled image params out of a PerfContext and gated the comparison behind an imageOptimization flag. This rewrite drops the context dependency, generalises the data shape to anything you can pull off a payload audit (id / label / bytes / dimensions / optional lazy flag), exposes the format-ratio table so consumers can plug in their own encoder numbers, and adds the dimensions column plus the savings chip.