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 KB1600×900
- card-168 KB640×480lazy
- card-279 KB640×480lazy
- banner63 KB1200×240lazy
Initial payloadwas 538 KB
WebP at quality 75. Total transferred 385 KB.175 KB−29%
Customize
Encoder
webp
75%
Display
Installation
npx shadcn@latest add https://craftbits.dev/r/image-opt-widget.jsonUsage
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 thecb-labelstyle) and adescriptionsub-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 optionallazybadge 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
showOriginalis set). - Footer. Renders the initial payload total against the baseline. A negative-percent chip reports the headline savings.
Understanding the component
- Format ratios. Each target format has a baseline compression ratio against JPEG quality 100 —
jpeg: 1,webp: 0.65,avif: 0.5. OverrideformatRatiosto plug in your own audit numbers. - 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. - Lazy entries. Entries with
lazy: truestill count towards the total transferred payload but drop out of the initial payload total in the footer. This mirrors aloading="lazy"image below the fold. - Verdicts. A row paints
goodwhen the format swap shaved at least 30% off the original,neutralfor smaller wins, andbadonly whileshowOriginalis on. The footer paintsgoodonce the initial-payload savings clear 30%,neutralat 10%, andbadbelow. - 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
| Prop | Type | Default | Description |
|---|---|---|---|
images | ImageOptWidgetImage[] | required | Ordered list of image entries. |
title | ReactNode | — | Optional heading above the rail. |
description | ReactNode | — | Optional sub-headline under the title. |
format | 'jpeg' | 'webp' | 'avif' | 'webp' | Target encoded format. |
quality | number | 75 | Encoder quality, 1 to 100. |
formatRatios | Partial<Record<format, number>> | — | Override the baseline compression ratios. |
hideTotal | boolean | false | Hide the initial-payload footer. |
showOriginal | boolean | false | Render bars at the baseline size. |
headingAs | 'h2' | 'h3' | 'h4' | 'h3' | Tag for the title element. |
className | string | — | Merged onto the root via cn(). |
ImageOptWidgetImage
| Field | Type | Description |
|---|---|---|
id | string | Stable identifier. |
label | ReactNode | Visible row label. Falls back to id. |
format | 'jpeg' | 'webp' | 'avif' | 'png' | 'gif' | Original format. Informational. |
bytes | number | Original encoded payload in bytes. |
dimensions | { w: number; h: number } | Pixel dimensions of the asset. |
lazy | boolean | Drop the asset from the initial-payload total. |
Accessibility
- The wrapper is a
<section>withdata-cb-edu="image-opt-widget"anddata-cb-verdictmirroring the cumulative verdict. Rows form arole="list"ofrole="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"anddata-cb-lazyso consumers can extend tone-specific styling without monkey-patching CSS. - The dimensions cell carries a descriptive
aria-labelso 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 aPerfContextand gated the comparison behind animageOptimizationflag. 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.