Image
Responsive image with aspect-ratio, object-fit, rounded corners, color and blur placeholder states, and a picture mode for art direction.
Import
import { Image } from "@hyzer-labs/ui"Demo
Image + Lightbox
Image has no click-to-view of its
own. To add it, pass an Image into a Lightbox component's trigger snippet, or wire the lightboxGroup attachment over
an Image grid you already render.<Image src="/photos/course.jpg" alt="Hole 7 fairway" aspectRatio="1/1" /><Image src="/photos/course.jpg" alt="Hole 7 fairway" aspectRatio="4/3" /><Image src="/photos/course.jpg" alt="Hole 7 fairway" aspectRatio="16/9" /><Image src="/photos/course.jpg" alt="Hole 7 fairway" aspectRatio="21/9" />fit maps to object-fit inside the aspect-ratio box. The demo source
is 21/9, letterboxed into a 4/3 frame, so the modes differ visibly.
<Image src="/photos/course.jpg" alt="Hole 7 fairway" aspectRatio="4/3" /><Image src="/photos/course.jpg" alt="Hole 7 fairway" aspectRatio="4/3" fit="contain" /><Image src="/photos/course.jpg" alt="Hole 7 fairway" aspectRatio="4/3" fit="fill" /><Image src="/photos/course.jpg" alt="Hole 7 fairway" aspectRatio="4/3" fit="none" /><Image src="/photos/course.jpg" alt="Hole 7 fairway" aspectRatio="1/1" /><Image src="/photos/course.jpg" alt="Hole 7 fairway" aspectRatio="1/1" rounded="sm" /><Image src="/photos/course.jpg" alt="Hole 7 fairway" aspectRatio="1/1" rounded="md" /><Image src="/photos/course.jpg" alt="Hole 7 fairway" aspectRatio="1/1" rounded="lg" /><Image src="/photos/course.jpg" alt="Hole 7 fairway" aspectRatio="1/1" rounded="full" />Placeholders fill the box while the image loads: color paints a flat
swatch, and blur crossfades from a low-res placeholderSrc. The
demo SVGs load instantly, so the effect is brief here.
<Image
src="/photos/course.jpg"
alt="Hole 7 fairway"
placeholder="color"
/><Image
src="/photos/course.jpg"
alt="Hole 7 fairway"
placeholder="blur"
placeholderSrc="/photos/course-tiny.jpg"
/>sources renders a <picture>: the browser takes the first
matching source, and falls back to src. Source media conditions are viewport queries, the way the platform defines them, so resize the
window across 968px to watch the labeled candidates swap.
<Image
src="/photos/course-narrow.jpg"
alt="Hole 7 fairway"
sources={[
{ srcset: '/photos/course.avif', type: 'image/avif' },
{ srcset: '/photos/course-wide.jpg', media: '(min-width: 968px)' }
]}
/>Props
| Name | Type | Default | Note |
|---|---|---|---|
src | string | — | Required. Fallback in picture mode. |
alt | string | — | Required. Empty string for decorative. |
sources | ImageSource[] | — | Renders a <picture>: candidates in priority order — see ImageSource below. src remains the fallback and drives the load state. |
width | number | — | |
height | number | — | |
loading | 'lazy' | 'eager' | 'lazy' | |
aspectRatio | 'auto' | '1/1' | '4/3' | '16/9' | '21/9' | string | 'auto' | |
fit | 'cover' | 'contain' | 'fill' | 'none' | 'cover' | |
rounded | boolean | 'sm' | 'md' | 'lg' | 'full' | false | true is shorthand for md. |
placeholder | 'blur' | 'color' | 'none' | 'none' | |
placeholderSrc | string | — | Low-res image for placeholder="blur"; required in that mode. |
placeholderColor | string | 'var(--hz-color-border)' | |
class | string | — | Merged after the hz-image class. |
ImageSource
| Name | Type | Default | Note |
|---|---|---|---|
srcset | string | — | Required. Candidate URL(s) for this source. |
type | string | — | MIME type for format negotiation, e.g. 'image/avif'. |
media | string | — | Viewport media query for art direction, e.g. (min-width: 968px). |
sizes | string | — |
Theme hooks
What this component promises your CSS. The reference theme styles exactly these — from @layer hz-theme, so your unlayered rules win. See Styling Components for the how.
Root class: .hz-image
Data attributes
| Hook | Values | Styles |
|---|---|---|
data-state | 'loading' | 'loaded' | 'error' | The load lifecycle. Read-only — the component owns it. Drives the placeholder crossfade and the error surface. |
data-fit | 'cover' | 'contain' | 'fill' | 'none' | object-fit on the inner img. Default cover. |
data-rounded | present | 'sm' | 'md' | 'lg' | 'full' | Corner radius. Bare presence (from rounded={true}) means md. |
data-aspect-ratio | 'auto' | '1/1' | '4/3' | '16/9' | '21/9' | any ratio | Mirrors the prop; the ratio itself lands as an inline style. The union is open — the listed values are documentation, not a limit. |
data-placeholder | 'blur' | 'color' | Absent when there is no placeholder — you cannot select [data-placeholder='none']. blur without placeholderSrc degrades to none. |
data-loading | 'lazy' | 'eager' | Mirrors the loading prop. |
data-picture | present in picture mode | Present when sources are supplied. |
Custom properties
| Hook | Values | Styles |
|---|---|---|
--hz-image-placeholder-blur | <length> — default 20px | Blur radius on the low-res placeholder. |
--hz-image-fade-duration | <time> — default 0.3s | Placeholder crossfade duration. Read at both ends of the fade, so the two stay locked together. |
Part classes
| Hook | Values | Styles |
|---|---|---|
.hz-image__img | child element | The real img. Note the BEM double underscore — Image and Video use it; the rest of the library uses a single dash. |
.hz-image__placeholder | child element | The decorative low-res image that fades out. |
.hz-image__picture | child element | The <picture> element. display: contents, so it adds no box. |
Accessibility
Pass descriptive alt text for informative images. Pass alt='' for decorative images: the component then sets role='presentation' for you.
References: MDN: <img> · MDN: <picture>