Image renders responsive raster and URL-based images as a native <img>, and trusted local SVGs as an inline <svg>. It is modeled on the Next.js Image layout and performance subset: intrinsic width/height, fill, objectFit, objectPosition, lazy loading, priority preloading, and blur placeholders. Use inline SVG mode when size and class changes must apply to the actual <svg> node (for example application logos).
Design-system Image (@prepared911/ui/image)
The design-system Image keeps the URL path (src, optional srcDark) and adds an inline SVG path:
Pass svg={Wordmark} where Wordmark is ComponentType<SVGProps<SVGSVGElement>> (an SVGR ?react import). Do not also pass src.
Size the mark with size={{ inline, block }}. Each axis is a space-scale step (--space-*), for example 32 or 80. There is no style, className, or numeric width / height.
Paint stays currentColor, so the mark follows surrounding text color.
A meaningful alt (LocalizedMessage) becomes role="img" and aria-label. decorative, or alt="", sets aria-hidden="true" and focusable="false".
Do not put title or desc in the SVG. They would duplicate the accessible name.
Icon and BrandMark remain the glyph set and the Axon product mark. Image is the wordmark or other trusted SVG component.
A ui-core call <Image svg={Mark} width={153} height={80} /> becomes <Image svg={Mark} size={{ inline: 128, block: 80 }} />. 153 is not a space step; 128 is the nearest step at or below it.
When to use
Logos, illustrations, and photos where intrinsic dimensions or responsive sizes help avoid layout shift.
Hero or card imagery that benefits from lazy loading, priority preloading, or a blur placeholder.
fill layouts inside a positioned container (covers, thumbnails).
Trusted local SVG logos via svg={ImportedSvg} when size and class changes must affect the actual <svg> node.
Decorative imagery with no semantic meaning — prefer Icon or hide from assistive tech with an empty alt only when appropriate.
Variants
Mode
Prop
Renders
When to choose
URL / raster
src
Native <img>
Photos, remote assets, URL SVGs (rendered safely as <img>)
Inline SVG
svg
Inline <svg> via SVGR (?react import)
Trusted local logos and illustrations that must resize with CSS
Themed URL
lightSrc / darkSrc
Two native <img> elements
Logos or marks that differ by light/dark theme
Themed inline SVG
lightSvg / darkSvg
Two inline <svg> elements
Trusted local logos that differ by light/dark theme
For URL SVGs, pass src and the component renders a native <img>. For trusted local SVGs imported via SVGR (?react), pass svg to render the actual <svg> element.
Theming
Pass lightSrc/darkSrc or lightSvg/darkSvg when a logo or mark needs a different asset in light and dark mode. Visibility follows the same theme class selectors as other ui-core styles:
Light: ancestor .light, .light-theme, or .radix-themes.light
Dark: ancestor .dark, .dark-theme, or .radix-themes.dark
Image renders both variants and uses CSS (display) to reveal the one that matches the active theme. This follows the Next.js theme-aware image patternopens in a new tab (render both, toggle with CSS) adapted to this repo's class-based theming. Benefits:
SSR-safe — no JavaScript theme branch at render time; the correct variant appears as soon as the theme class is on <html>.
No flash — the off-theme variant is hidden before paint when the theme class is set server-side.
Accessible — the hidden variant uses display: none, so it is removed from the accessibility tree (no duplicate alt announcement).
In themed modes, id is applied to a display: contents themed root (not to either variant) so SSR and hydration stay aligned without useTheme() branching. onLoad and onError are forwarded to both variants so a background-loaded image still notifies consumers after a theme switch.
Name assets by contrast (for example logo-dark-new.svg is the dark mark used on light backgrounds), not by the active theme. Next.js apps that rely heavily on next/image optimization may still use a local wrapper (for example docs ThemedImage) for raster-heavy imagery.
Anatomy
URL mode: A single <img> with BEM classes for fill, object-fit, object-position, and optional blur placeholder. When sources is set, that <img> sits inside a layout-transparent <picture> so fill / object-fit still resolve on the image itself.
Inline SVG mode: The imported SVG component with the same layout BEM classes applied directly to the <svg> node. Numeric width/height are forwarded as SVG attributes (matching URL mode); omit them to use intrinsic/viewBox sizing.
Themed modes: Both light and dark sources mount; CSS reveals the active variant.
Responsive and format negotiation
URL mode accepts:
Prop
Role
srcSet
Native srcset candidate set; pair with sizes so the browser can pick a width
sizes
Native sizes hint — only meaningful alongside srcSet or a sources entry
sources
Array of { srcSet, type?, media?, sizes? } rendered as <source> elements ahead of src
ImageMediaType covers the common MIME gates (Avif, Webp, Png, Jpeg, Gif, Svg). Order sources newest-first; keep src as a universally supported fallback.
Content guidelines
Alt text: Every meaningful image needs a concise alt that describes purpose, not decoration. Pass decorative instead only when the image is purely decorative and surrounding context carries the meaning.
Dimensions: Provide width and height (or fill inside a sized container) to reserve layout space and reduce cumulative layout shift.
Logos: Prefer inline SVG mode for brand marks that must scale crisply at multiple sizes.
Behavior and states
Loading: Defaults to lazy loading (ImageLoading.Lazy). Set priority for above-the-fold imagery (eager load, high fetch priority).
Placeholder:ImagePlaceholder.Blur with blurDataURL shows a low-resolution preview until the main image loads (URL mode only). Changing src resets the blur state so the placeholder can reappear.
Object fit / position:ImageObjectFit and ImageObjectPosition map to CSS via BEM modifiers.
Fill: When fill is true, omit width/height and place Image inside a positioned ancestor.
Server-only Next.js Image props (loader, quality, unoptimized) are not supported in ui-core.
Best practices
Do
Reserve space with intrinsic dimensions or a sized fill container.
Use priority sparingly for hero or LCP imagery.
Import trusted SVGs with ?react and pass them via svg when CSS must target the SVG node.
Use lightSvg/darkSvg or lightSrc/darkSrc for theme-aware logos instead of hand-rolling useTheme() branches.
Don't
Inline remote or untrusted SVG markup — use src (native <img>) instead.
Branch on useTheme() in render to swap logo src/svg — use themed props so CSS handles the toggle.
Use Image for icon-sized glyphs — use Icon.
Rely on images alone to convey critical information — pair with visible text.
Accessibility
Required alt for non-decorative images; empty alt for decorative images only.
Inline SVG mode sets role="img" and aria-label when alt is non-empty; decorative images get aria-hidden and focusable={false}.