Cover Image
CoverImage renders images that fill a container - the container dictates the dimensions and the image stretches and crops to cover it with object-fit: cover applied by default. Think of it as the accessible, SEO-friendly replacement for background-image: cover.
Use CoverImage when the image is a backdrop: hero banners, card backgrounds, split-layout panels, full-bleed sections, or anywhere a fixed-size container needs an image filling it edge to edge.
Hero Banner
The container controls the size - the image stretches and crops to fill it.
import { CoverImage } from "@/registry/rokkit200-ui/components/cover-image/cover-image";import type { ImageUrlBuilder, NormalizedImageSource, PictureProfile, UrlTransform,} from "@/registry/rokkit200-ui/components/image/image-types";
/** * A demo-only URL builder for placehold.co that rewrites the path-encoded * dimensions and appends a text label showing the requested width. * This makes each srcset candidate visually distinct so you can resize * the browser and see the browser pick a different resolution. */const placeholdDemoBuilder: ImageUrlBuilder = { canHandle: image => { try { return new URL(image.src).hostname.endsWith("placehold.co"); } catch { return false; } }, build: (image, transform: UrlTransform) => { try { const url = new URL(image.src); const w = (transform.width as number | undefined) ?? image.width; const h = (transform.height as number | undefined) ?? (w != null && image.width && image.height ? Math.round(w * (image.height / image.width)) : image.height);
url.pathname = url.pathname.replace(/^\/\d+x\d+/, `/${w}x${h}`); url.searchParams.set("text", `${w}x${h}`);
return url.toString(); } catch { return image.src; } },};
const image: NormalizedImageSource = { src: "https://placehold.co/1200x600", width: 1200, height: 600, metadata: { alt: "Aerial cityscape used as a hero banner background", },};
const profile: PictureProfile = { id: "cover-default", srcSetWidths: [600, 900, 1200], sizes: ["100vw"],};
/** * CoverImage renders a backdrop whose dimensions are controlled by its * container - not by the image itself. The image stretches and crops to * fill the container with `object-fit: cover` applied by default. * * Typical uses: hero banners, card backgrounds, split-layout panels, * full-bleed sections - anywhere the layout dictates the box size and the * image adapts to fill it. */export default function CoverImageDefaultDemo() { return ( <div className="relative h-64 w-full overflow-hidden rounded-lg"> <CoverImage imageSource={image} profile={profile} urlBuilder={placeholdDemoBuilder} /> <div className="relative z-10 flex h-full items-end p-6"> <div className="rounded bg-black/60 px-4 py-2 text-white"> <p className="text-lg font-semibold">Hero Banner</p> <p className="text-sm text-white/80"> The container controls the size - the image stretches and crops to fill it. </p> </div> </div> </div> );}Examples
Section titled “Examples”Art Directed
Section titled “Art Directed”Art-directed cover images use <picture> with multiple sources for different breakpoints. Each source can provide a different crop or aspect ratio suited to the viewport.
Hero Banner
The container controls the size - the image stretches and crops to fill it.
import { CoverImage } from "@/registry/rokkit200-ui/components/cover-image/cover-image";import type { ArtDirectedImageSource, ImageUrlBuilder, PictureProfile, UrlTransform,} from "@/registry/rokkit200-ui/components/image/image-types";
/** * Demo-only placehold.co builder - rewrites the path dimensions and stamps * the requested size as visible text so each srcset candidate is distinct. */const placeholdDemoBuilder: ImageUrlBuilder = { canHandle: image => { try { return new URL(image.src).hostname.endsWith("placehold.co"); } catch { return false; } }, build: (image, transform: UrlTransform) => { try { const url = new URL(image.src); const w = (transform.width as number | undefined) ?? image.width; const h = (transform.height as number | undefined) ?? (w != null && image.width && image.height ? Math.round(w * (image.height / image.width)) : image.height);
url.pathname = url.pathname.replace(/^\/\d+x\d+/, `/${w}x${h}`); url.searchParams.set("text", `${w}x${h}`);
return url.toString(); } catch { return image.src; } },};
type ProfileKey = "default" | "desktop" | "tablet" | "mobile";
const artDirectedSources: ArtDirectedImageSource<ProfileKey> = { fallback: { src: "https://placehold.co/800x400", width: 800, height: 400, metadata: { alt: "Art-directed cover image" }, }, sources: [ { media: "(min-width: 1920px)", image: { src: "https://placehold.co/1920x600", width: 1920, height: 600, }, profileKey: "desktop", }, { media: "(min-width: 1280px)", image: { src: "https://placehold.co/1280x500", width: 1280, height: 500, }, profileKey: "tablet", }, { media: "(min-width: 768px)", image: { src: "https://placehold.co/768x400", width: 768, height: 400, }, profileKey: "mobile", }, ],};
const profileMap: Record<ProfileKey, PictureProfile> = { default: { id: "cover-fallback", srcSetWidths: [400, 600, 800], sizes: ["100vw"], }, desktop: { id: "cover-desktop", srcSetWidths: [1280, 1600, 1920], sizes: ["100vw"], }, tablet: { id: "cover-tablet", srcSetWidths: [768, 1024, 1280], sizes: ["100vw"], }, mobile: { id: "cover-mobile", srcSetWidths: [480, 600, 768], sizes: ["100vw"], },};
export default function CoverImageArtDirectedDemo() { return ( <div className="relative h-64 w-full overflow-hidden rounded-lg"> <CoverImage artDirectedImageSource={artDirectedSources} profileMap={profileMap} urlBuilder={placeholdDemoBuilder} className="overflow-hidden rounded-lg" /> <div className="relative z-10 flex h-full items-end justify-end p-6"> <div className="rounded bg-black/60 px-4 py-2 text-white"> <p className="text-lg font-semibold">Hero Banner</p> <p className="text-sm text-white/80"> The container controls the size - the image stretches and crops to fill it. </p> </div> </div> </div> );}Object Position
Section titled “Object Position”Override the default object-position: center when the focal point of the image is off-center. This example shows top-right positioning.
Hero Banner
The container controls the size - the image stretches and crops to fill it.
import { CoverImage } from "@/registry/rokkit200-ui/components/cover-image/cover-image";import type { ImageUrlBuilder, NormalizedImageSource, PictureProfile, UrlTransform,} from "@/registry/rokkit200-ui/components/image/image-types";
/** * Demo-only placehold.co builder. */const placeholdDemoBuilder: ImageUrlBuilder = { canHandle: image => { try { return new URL(image.src).hostname.endsWith("placehold.co"); } catch { return false; } }, build: (image, transform: UrlTransform) => { try { const url = new URL(image.src); const w = (transform.width as number | undefined) ?? image.width; const h = (transform.height as number | undefined) ?? (w != null && image.width && image.height ? Math.round(w * (image.height / image.width)) : image.height);
url.pathname = url.pathname.replace(/^\/\d+x\d+/, `/${w}x${h}`); url.searchParams.set("text", `${w}x${h}`);
return url.toString(); } catch { return image.src; } },};
const image: NormalizedImageSource = { src: "https://placehold.co/1200x800", width: 1200, height: 800, metadata: { alt: "Cover image with custom object-position - the focal point is offset toward the top-right", },};
const profile: PictureProfile = { id: "cover-position", srcSetWidths: [600, 900, 1200], sizes: ["100vw"],};
/** * Demonstrates the `objectPosition` prop which overrides the default * `object-position: center` on the `<img>`. Useful when the subject * of interest is off-center (e.g. a person's face in the top-right). */export default function CoverImageObjectPositionDemo() { return ( <div className="relative h-64 w-full overflow-hidden rounded-lg"> <CoverImage imageSource={image} profile={profile} urlBuilder={placeholdDemoBuilder} objectPositionClassName="object-top-right" className="overflow-hidden rounded-lg" /> <div className="relative z-10 flex h-full items-start justify-end p-6"> <div className="rounded bg-black/60 px-4 py-2 text-white"> <p className="text-lg font-semibold">Hero Banner</p> <p className="text-sm text-white/80"> The container controls the size - the image stretches and crops to fill it. </p> </div> </div> </div> );}srcSet Override
Section titled “srcSet Override”Bypass the planner-generated srcset with a pre-built string (e.g. from a CMS). Width descriptors are validated against resolvable sizes - if sizes cannot be resolved, the override is discarded with a dev warning.
Hero Banner
The container controls the size - the image stretches and crops to fill it.
import { CoverImage } from "@/registry/rokkit200-ui/components/cover-image/cover-image";import type { NormalizedImageSource, PictureProfile,} from "@/registry/rokkit200-ui/components/image/image-types";
const image: NormalizedImageSource = { src: "https://placehold.co/1200x600", width: 1200, height: 600, metadata: { alt: "Cover image with a CMS-provided srcSet override", },};
/** * Profile with empty srcSetWidths - the planner won't generate its own srcset. * Sizes are still required so the overridden width-descriptor srcset works. */const profile: PictureProfile = { id: "srcset-override", srcSetWidths: [], sizes: ["100vw"],};
/** * A pre-built srcset string, as might be provided by a CMS. * Because it uses width descriptors (`w`), CoverImage validates that * sizes are resolvable before forwarding the override to the planner. */const cmsSrcSet = [ "https://placehold.co/600x300 600w", "https://placehold.co/900x450 900w", "https://placehold.co/1200x600 1200w",].join(", ");
export default function CoverImageSrcSetOverrideDemo() { return ( <div className="relative h-64 w-full overflow-hidden rounded-lg"> <CoverImage imageSource={image} profile={profile} srcSetOverride={cmsSrcSet} className="overflow-hidden rounded-lg" /> <div className="relative z-10 flex h-full items-start p-6"> <div className="rounded bg-black/60 px-4 py-2 text-white"> <p className="text-lg font-semibold">Hero Banner</p> <p className="text-sm text-white/80"> The container controls the size - the image stretches and crops to fill it. </p> </div> </div> </div> );}Warning: Empty alt Text
Section titled “Warning: Empty alt Text”A non-decorative image with no alt text triggers a dev warning. Either provide an alt prop, populate metadata.alt on the source, or mark the image as decorative.
Hero Banner
The container controls the size - the image stretches and crops to fill it.
import { CoverImage } from "@/registry/rokkit200-ui/components/cover-image/cover-image";import type { NormalizedImageSource, PictureProfile,} from "@/registry/rokkit200-ui/components/image/image-types";
/** * Image with no alt text in metadata and no alt prop override. * The planner emits a dev warning for non-decorative images with empty alt. * * Open the browser console to see: * [image] Non-decorative image has empty alt text. */const image: NormalizedImageSource = { src: "https://placehold.co/1200x600", width: 1200, height: 600,};
const profile: PictureProfile = { id: "warn-empty-alt", srcSetWidths: [600, 1200], sizes: ["100vw"],};
export default function CoverImageWarnEmptyAltDemo() { return ( <div className="relative h-64 w-full overflow-hidden rounded-lg"> <CoverImage imageSource={image} profile={profile} className="overflow-hidden rounded-lg" /> <div className="relative z-10 flex h-full items-end p-6"> <div className="rounded bg-black/60 px-4 py-2 text-white"> <p className="text-lg font-semibold">Hero Banner</p> <p className="text-sm text-white/80"> The container controls the size - the image stretches and crops to fill it. </p> </div> </div> </div> );}Warning: Missing Dimensions
Section titled “Warning: Missing Dimensions”An image without width / height triggers a dev warning. Pass suppressMissingDimensionsWarning for intentionally dimension-free images such as SVGs or data URIs.
Hero Banner
The container controls the size - the image stretches and crops to fill it.
import { CoverImage } from "@/registry/rokkit200-ui/components/cover-image/cover-image";import type { NormalizedImageSource, PictureProfile,} from "@/registry/rokkit200-ui/components/image/image-types";
/** * Image deliberately missing width and height. * The planner emits a dev warning when dimensions are absent. * Pass `suppressMissingDimensionsWarning` to silence this for intentional cases. * * Open the browser console to see: * [image] Image is missing width. */const image: NormalizedImageSource = { src: "https://placehold.co/1200x600", metadata: { alt: "Cover image without explicit dimensions", },};
const profile: PictureProfile = { id: "warn-missing-dims", srcSetWidths: [600, 1200], sizes: ["100vw"],};
export default function CoverImageWarnMissingDimensionsDemo() { return ( <div className="relative h-64 w-full overflow-hidden rounded-lg"> <CoverImage imageSource={image} profile={profile} className="overflow-hidden rounded-lg" /> <div className="relative z-10 flex h-full items-end justify-end p-6"> <div className="rounded bg-black/60 px-4 py-2 text-white"> <p className="text-lg font-semibold">Hero Banner</p> <p className="text-sm text-white/80"> The container controls the size - the image stretches and crops to fill it. </p> </div> </div> </div> );}Warning: Missing Sizes
Section titled “Warning: Missing Sizes”A profile with srcSetWidths but no sizes causes the planner to discard the generated srcset and emit a dev warning. Provide sizes on the profile or via the sizes prop.
Hero Banner
The container controls the size - the image stretches and crops to fill it.
import { CoverImage } from "@/registry/rokkit200-ui/components/cover-image/cover-image";import type { NormalizedImageSource, PictureProfile,} from "@/registry/rokkit200-ui/components/image/image-types";
/** * Profile with srcSetWidths but no sizes. When w descriptors are generated * but sizes cannot be resolved, the planner discards the srcset and emits * a dev warning. * * Open the browser console to see: * [image] Profile "warn-missing-sizes" has no sizes defined and no sizes * override was provided. srcset with w descriptors requires sizes - * srcset will be omitted. */const image: NormalizedImageSource = { src: "https://placehold.co/1200x600", width: 1200, height: 600, metadata: { alt: "Cover image with a profile missing sizes", },};
const profile: PictureProfile = { id: "warn-missing-sizes", srcSetWidths: [600, 1200], sizes: [],};
export default function CoverImageWarnMissingSizesDemo() { return ( <div className="relative h-64 w-full overflow-hidden rounded-lg"> <CoverImage imageSource={image} profile={profile} className="overflow-hidden rounded-lg" /> <div className="relative z-10 flex h-full items-start justify-end p-6"> <div className="rounded bg-black/60 px-4 py-2 text-white"> <p className="text-lg font-semibold">Hero Banner</p> <p className="text-sm text-white/80"> The container controls the size - the image stretches and crops to fill it. </p> </div> </div> </div> );}Below is an example of how to use CoverImage in your site.
import { CoverImage } from "@/rokkit200-ui/components/cover-image/cover-image";
{/* Hero banner - the container controls the size, image fills it */}<div className="relative h-96 w-full"> <CoverImage imageSource={{ src: "/photos/hero-banner.jpg", width: 1200, height: 600, metadata: { alt: "Hero banner" }, }} profile={{ id: "hero", srcSetWidths: [600, 900, 1200], sizes: ["100vw"], }} /> <div className="relative z-10 p-8"> <h1>Your heading over the cover image</h1> </div></div>API Reference
Section titled “API Reference”Normalized (single-source) props
Section titled “Normalized (single-source) props”| Prop | Type | Default | Description |
|---|---|---|---|
imageSource | NormalizedImageSource | — | The image data object (src, width, height, metadata). Required (XOR with art-directed props). |
profile | PictureProfile | — | Responsive profile controlling srcset widths, sizes, format, quality, and fit. Required with imageSource. |
sizes | string | — | Override sizes from the profile for this specific instance. Optional. |
Art-directed (multi-source) props
Section titled “Art-directed (multi-source) props”| Prop | Type | Default | Description |
|---|---|---|---|
artDirectedImageSource | ArtDirectedImageSource<K> | — | Fallback image plus breakpoint-specific sources for <picture> rendering. Required (XOR with normalized props). |
profileMap | Record<K, PictureProfile> | — | Map of profile keys to profiles, one per source entry. Required with artDirectedImageSource. |
Common props
Section titled “Common props”| Prop | Type | Default | Description |
|---|---|---|---|
alt | string | — | Override alt text. Falls back to imageSource.metadata.alt. Optional. |
decorative | boolean | false | Mark as decorative (renders alt=""). Optional. |
loading | "lazy" | "eager" | "lazy" | HTML loading strategy. Optional. |
fetchPriority | "high" | "low" | "auto" | — | Fetch priority hint for the browser. Optional. |
decoding | "async" | "sync" | "auto" | "async" | Decoding strategy. Defaults to async for cover images. Optional. |
urlBuilder | ImageUrlBuilder | passthrough | CDN-specific URL builder. Optional. |
objectPositionClassName | string | "object-center" | CSS object-position applied to the <img>. Shift the crop focal point away from center. Optional. |
className | string | — | Applied to the outermost element (<img> or <picture>). Optional. |
style | React.CSSProperties | — | Inline styles on the outermost element. Optional. |
imageClassName | string | — | Applied to the <img> inside <picture> (art-directed only). Composed with object-cover by default. Optional. |
imageStyle | React.CSSProperties | — | Inline styles on the <img> inside <picture>. Optional. |
srcSetOverride | string | — | Bypass planner-generated srcset with a pre-built string. Width descriptors require resolvable sizes or the override is discarded. Optional. |
suppressWarnings | boolean | false | Silence all dev warnings. Optional. |
suppressMissingDimensionsWarning | boolean | false | Silence the missing-dimensions warning only. Optional. |
suppressMissingSizesWarning | boolean | false | Silence the missing-sizes warning only. Optional. |
suppressEmptyAltWarning | boolean | false | Silence the empty-alt warning only. Optional. |