Skip to contentAsheeUIAsheeUI

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 its alt and by nothing else. Image never invents a description: state alt, and use alt="" 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 alt like the element is. If it drops the prop, the picture is unnamed, and it needs the dimensions or the fill its own contract requires, which componentProps is for.