Bonusway design system

Data display

Picture

The one way this app renders an image. It wraps next/image so three decisions live in one place: whether the optimizer may fetch this URL at all (@/config/imageHosts answers, and the same list builds next.config.ts’s remotePatterns), how hard it may compress it (@/config/imageQuality, which builds images.qualities the same way), and what to draw when there is no src — a fallback, never a broken `<img>`.

import Picture from '@/components/ui/Picture';

Source: src/components/ui/Picture.tsx

Examples

width + height, or fill

Give the intrinsic size, or say fill and let a positioned parent decide. Tailwind’s preflight puts height:auto on every image, so width/height are the aspect ratio and the srcset — the rendered box is whatever the CSS says. With fill, always pass sizes.

No src, no broken image

API image fields are routinely null, and `<img src="">` requests the current page as an image: a broken icon that still takes layout space. An empty (or blank) src renders the fallback instead — or nothing, when there is none.

K

What gets optimized

Left: a local asset, served through /_next/image as WebP at the size it is drawn. Right: an SVG, which is left alone — dangerouslyAllowSVG is off, and a vector has nothing to gain. The same as-is treatment covers unknown hosts, blob: previews and data: URIs, so a partner CDN’s image is never a 400 from the optimizer.

Bonusway

Three compression steps

quality picks how hard the optimizer compresses. 60 for logos, avatars, icons and thumbnails — flat marks where the extra bytes buy nothing visible. 75 (the default, so it can be left off) for product and hero photography. 85 for the carousel’s full-bleed banner art, where type is set into the image and artefacts show. Only the values in @/config/imageQuality are served: Next coerces anything else to the nearest one listed, silently, so a number outside that list is a different image rather than an error.

Props

<Picture> props

These props come in alternative sets

The type is a union, so some combinations below are not valid together — passing one prop can require or exclude another. The component file states which.

PropTypeDefaultDescription
filltrue | false

Stretch to a positioned parent (`relative`/`absolute`/`fixed`) and take no intrinsic size. Pair it with `object-cover`/`object-contain` in `className` and always pass `sizes`.

widthnever | number

The image's intrinsic width in px — the aspect ratio and the srcset come from it.

heightnever | number

The image's intrinsic height in px.

srcrequiredfrom PictureBasePropsstring | null | undefined

The image URL. Nullable on purpose: almost every image in this app comes from an API field that may be absent, and the point of this component is that a missing one renders `fallback` rather than a broken `<img>`.

altrequiredfrom PictureBasePropsstring

Empty string for decorative art, as the HTML spec asks.

classNamefrom PictureBasePropsstring
stylefrom PictureBasePropsCSSProperties

Prefer `className`. This is for the rare box that has to beat Tailwind's preflight (`max-width: 100%`, `height: auto`) with a specificity a utility class cannot reach — the app-store badges, which a narrow menu panel silently squeezed before they were pinned this way.

sizesfrom PictureBasePropsstring

Required whenever the image stretches — with `fill`, or with CSS that makes it responsive. Without it the browser assumes the image is as wide as the viewport and downloads the largest candidate in the srcset.

fallbackfrom PictureBasePropsReactNodenull

Rendered in place of the image when `src` is empty. Nothing, by default.

loadingfrom PictureBaseProps'lazy' | 'eager'

`lazy` by default (that is `next/image`'s own default, and a change from the raw `<img>` these replaced, which were all eager). Set `eager` for anything above the fold — a page's hero, the header logo — where lazy loading trades a little bandwidth for a visible pop-in and a worse LCP.

unoptimizedfrom PictureBasePropsboolean

Forces the image to be served as-is even when the host IS allowlisted. The one case that needs it: a layout whose box is the image's OWN intrinsic size (no width and no height in CSS, only `max-*` caps). Optimizing such an image attaches a density-descriptor srcset, and since the optimizer never enlarges a source, a 2× candidate that comes back at the source's own width renders at HALF the size on a retina screen. There is no `sizes` value that fixes it — the fix is to not optimize.

qualityfrom PictureBasePropsImageQuality
fetchPriorityfrom PictureBaseProps'high' | 'low' | 'auto'

everywhere else, since marking everything high marks nothing high.

reffrom PictureBasePropsRef<HTMLImageElement>

For the error-fallback pattern (see Avatar), which needs the element itself.

onErrorfrom PictureBasePropsReactEventHandler<HTMLImageElement>

Client components only — a server component cannot serialise a function.

Accessibility

  • alt is required, and an empty string is the right answer for decorative art — it is how a screen reader is told to skip it rather than read a filename.
  • A fallback that carries meaning (a store’s initial) should be text, not an empty box: it is what replaces the image for everyone, not just for assistive tech.

Denna webbplats använder cookies.

Vi använder cookies för att förbättra din upplevelse på vår webbplats och anpassa innehållet vi tillhandahåller dig. Viktiga cookies är nödvändiga för att vår tjänst ska fungera korrekt. Om du går med på att vi samlar in information om dig genom användningen av nödvändiga och marknadsförande cookies, välj "Acceptera alla". Du kan alltid ändra dina preferenser genom att besöka Cookie -inställningar.

Cookie -inställningar