Image
A picture whose frame is stated before the file arrives.
A picture arrives after the layout has already been decided, so the space it will take is
the one thing on the page nobody knows in advance, and the jump it makes when it lands is
the layout shift everybody notices and nobody attributes. So Image states the frame
first: ratio claims the space, fit decides how the picture fills it, a skeleton holds
the frame until the file is there, and fallbackSrc covers the source that never arrives.
What separates it from its siblings is the scope. Card.image composes this component
inside a card's own placement rules and Avatar composes it in a fixed circular frame;
Image is the picture on its own, with the frame stated by the page, and the two seams
the others inherit, the error fallback and the framework's own image component, are stated
here.
Usage
import { Image } from "@asheeui/web";
<Image src="/campaign.png" alt="Autumn campaign artwork" ratio="video" />
A source and its alternative text are the whole requirement. Everything else has a value
already: an auto ratio, a cover fit, the application's defaultRadius, lazy loading and a
skeleton that shows until the picture is there.
Ratio and fit
ratio takes four keys: auto, which is the source's own proportions; square, 1:1;
video, 16:9; and portrait, 3:4. The frame is drawn from the ratio rather than from the
file, so a column of pictures settles once instead of reflowing as each source arrives.
fit is the element's own object-fit, with the five behaviours the browser defines:
cover crops the source to the frame, contain letterboxes it inside the frame, fill
stretches it, none keeps it at its natural size and scale-down is the smaller of none
and contain.
Both options are registered, so a page of product photography states components.image.fit
and components.image.ratio once rather than on every picture. ratio="auto" is the
absence of a frame: the height then comes from the file, which is what one picture inside a
story wants and what a row of thumbnails does not.
Loading and the error fallback
Until the picture is there, the frame holds a skeleton: a pulsing wash, hidden from assistive technology, removed as soon as the source reports itself loaded. The picture fades in over 300ms, so the arrival is a fade rather than a flash. A source the browser has already cached reports itself complete when the element mounts, so a picture the reader has seen before renders without the placeholder at all.
fallbackSrc is the second source. When the first fails, Image swaps it in and tries
again; onError is called only when there is nothing left to try, which is either no
fallback or a fallback that failed too. So a handler is about the picture the reader did
not get rather than the one that was replaced. onLoad follows the same rule from the
other side: it reports the picture that finally renders, so with a fallback in play it
fires for the substitute.
showSkeleton (registered true) turns the placeholder off, which is what a picture
behind text wants. loading (registered lazy) is the browser's own deferral, eager is
the right value for the picture above the fold. It is the web's option alone: a native
picture is a view, fetched when the view mounts, so the native renderer's configuration
omits it rather than accepting a value it would ignore.
A framework's image
A framework's image component knows what a bare <img> does not, the intrinsic size, the
loader, the allowed host, so Image renders component in place of the element and
forwards componentProps to it. The substitute receives the element's own props, src,
alt, className, loading and the load and error handlers, with componentProps merged
last, so a key stated in both places is the component's.
import NextImage from "next/image";
<Image
src="/campaign.png"
alt="The autumn campaign"
ratio="video"
component={NextImage}
componentProps={{ sizes: "(min-width: 768px) 50vw, 100vw" }}
/>
When component is stated and componentProps gives neither width/height nor fill,
Image adds fill: true and sizes: "100%", because the frame is what decides the shape:
the picture fills it rather than carrying its own dimensions. loading and priority are
forwarded exactly as given, a substituted component's own defaults are its business, so
Image supplies loading: "eager" only when neither the instance nor componentProps states
one, and never rewrites priority. This is the same seam every navigation component uses for
its links, see Links and substitution.
Props
fit, ratio, loading and showSkeleton are registered with the values above, so
components.image retunes every picture in the application at once.
radius is deliberately not registered, so a picture follows the application's
defaultRadius exactly as a control does: the framework's own configuration rounds both to
xs, and md is only the fallback when no tier states one. That is what lets a product state
its rounding once; an application that wants pictures rounder than its controls states
components.image.radius.
src, alt, fallbackSrc, component, componentProps and the native attributes are
instance-only, because a source, its description and the component that renders it are
statements about one picture rather than about the design.
Accessibility
- The element is a real
<img>inside a<span>that carries only the frame, so the picture is announced by itsaltand by nothing else.Imagenever invents a description: statealt, and usealt=""for a picture that is decoration. - The skeleton is
aria-hidden, so a reader is never told about a placeholder that is about to be replaced. - The frame is the ratio, so the space is claimed before the request starts: a reader's place in the page does not move when the picture loads.
- A source with no working fallback keeps its alternative text on screen, so a failed picture is still described rather than left as an empty box.
- A substituted component is given
altlike the element is. If it drops the prop, the picture is unnamed, and it needs the dimensions or thefillits own contract requires, whichcomponentPropsis for.