OsirionResponsiveImage
OsirionResponsiveImage renders an <img> that keeps its space while it loads, offers the browser files sized for
the screen, and loads lazily unless it is the page's main image. It is plain HTML, so it needs no script.
Package Osirion.Blazor.Core, namespace Osirion.Blazor.Components. Source on GitHub. Available from 4.0.
@using Osirion.Blazor.Components
<div style="max-width: 32rem;">
<OsirionResponsiveImage Src="/assets/images/blazor-cover.png"
Alt="A browser window showing the Blazor logo, on a purple background"
Width="1400"
Height="762"
SrcSet="/assets/images/blazor-cover.png 1400w"
Sizes="(min-width: 576px) 32rem, 100vw"
Caption="The image keeps its space while it loads." />
</div>Usage
@using Osirion.Blazor.Components
<OsirionResponsiveImage Src="/images/team.jpg" Alt="The team at the 2026 meetup" Width="1200" Height="800" />
This renders a lazily loaded, asynchronously decoded image with width and height, so the browser reserves its
space before the file arrives.
With an image service or CDN that resizes on request, give the widths to offer and how to build each file's URL:
<OsirionResponsiveImage Src="@PhotoAt(1600)"
Alt="A laptop showing charts on a desk"
Width="1600"
Height="1066"
Widths="imageWidths"
SrcFormatter="(_, width) => PhotoAt(width)"
Sizes="(min-width: 992px) 720px, 100vw"
Priority="true"
Caption="Monthly figures for the second quarter." />
@code {
private readonly int[] imageWidths = [640, 960, 1280, 1600];
private static string PhotoAt(int width) =>
$"https://images.example.com/laptop.jpg?w={width}&h={width * 2 / 3}&fit=crop";
}
The srcset becomes four candidates from 640 to 1600 pixels wide. Priority marks this as the main image of the
page, so it loads at once instead of lazily. images.example.com stands for your own image service.
Without an image service, write the candidates yourself with SrcSet, one file per width:
<OsirionResponsiveImage Src="/img/a-1280.jpg"
Alt="A chart of monthly sign-ups"
Width="1280"
Height="720"
SrcSet="/img/a-640.jpg 640w, /img/a-1280.jpg 1280w"
Sizes="(min-width: 768px) 50vw, 100vw" />
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
Src |
string |
"" |
The image URL, written to src. Browsers that ignore srcset load it, and it is the first argument of SrcFormatter. Required. |
Alt |
string |
"" |
The text alternative. Describe what the image shows; use an empty string only for an image that is purely decorative, which screen readers then skip. Required. |
Width |
int? |
null |
The image's intrinsic width in pixels, written to width. With Height it reserves the space before the file arrives, so the content below does not jump (Cumulative Layout Shift). When set it must be greater than zero, or the component throws ArgumentOutOfRangeException. |
Height |
int? |
null |
The image's intrinsic height in pixels, written to height. See Width; the same rule applies. |
SrcSet |
string? |
null |
The srcset candidates as written, for example /img/a-640.jpg 640w, /img/a-1280.jpg 1280w. Takes precedence over Widths. |
Widths |
IReadOnlyList<int>? |
null |
The widths, in pixels, to offer in srcset. They are sorted, and duplicates and values of zero or less are dropped. Each candidate's URL comes from SrcFormatter; without one this parameter is ignored. |
SrcFormatter |
Func<string, int, string>? |
null |
How the URL of the file of a given width in pixels is built from Src, for an image service such as (src, width) => $"{src}?w={width}". Used with Widths. |
Sizes |
string? |
null |
The sizes attribute: how wide the image is shown, for example (min-width: 768px) 50vw, 100vw. It is written only when a srcset exists. When it is not set, lazily loaded images use auto, 100vw (the browser measures the layout where it can) and Priority images use 100vw. |
Priority |
bool |
false |
Whether this is the page's main image, typically the largest one in the first view. It then loads at once (loading="eager") with fetchpriority="high" instead of lazily, which speeds up Largest Contentful Paint. Mark only one image per page. |
Caption |
string? |
null |
A caption. When it is not empty, the image is wrapped in figure with the caption in a figcaption; the CSS classes and extra attributes stay on the img. |
Theme |
ThemeMode |
ThemeMode.System |
The component's color mode. Light or Dark writes it to data-theme (data-bs-theme under Bootstrap) on the root element; System writes nothing. |
Class |
string? |
null |
Extra CSS classes for the component's root element. |
Style |
string? |
null |
An inline style for the component's root element. |
Unmatched attributes and Class go on the <img>, not on the <figure>. Style is accepted but the component does
not write it.
Behavior
- Every image gets
decoding="async". WithoutPriorityit also getsloading="lazy"; withPriorityit getsloading="eager"andfetchpriority="high". Mark only one image per page asPriority, the one in the first view: lazy loading that image would delay Largest Contentful Paint. Widthsare sorted, duplicates and values of zero or less are dropped. If nothing is left, nosrcsetis written.- Every file in the
srcsetshould have the aspect ratio ofWidthandHeight. The stylesheet setsheight: autoandmax-width: 100%, so the image scales down inside its container and keeps that ratio. WidthorHeightset to zero or a negative number throwsArgumentOutOfRangeException.- Without
WidthandHeightthe attributes are left out and the browser cannot reserve the space. - URLs are written as attributes, so Razor encodes them. The component does not check where they point; pass URLs you control or have validated.
Styling
| Class | Element |
|---|---|
osirion-responsive-image |
The <img>: display: block, max-width: 100%, height: auto |
osirion-responsive-image-figure |
The <figure> when there is a caption |
osirion-responsive-image-caption |
The <figcaption> |
The figure's bottom margin uses --osirion-spacing-4, and the caption uses --osirion-spacing-2,
--osirion-font-size-sm and --osirion-text-secondary, each with a fallback value.