Browse docs
Browse docs
A thin, declarative wrapper over the native <img>. It renders a URL you give it — no data fetching, no eval — and adds the three things a bare <img> lacks: it reserves the box before load (no layout shift), shows a skeleton while loading, and degrades to a graceful fallback on error.
import { Image } from '@dashforge/tw';
<Image src="/hero.jpg" alt="Team at work" aspectRatio={16 / 9} rounded="lg" />Give Image a src, an alt, and a shape — either aspectRatio or a fixed width + height. The shape reserves space so the layout never jumps when the image arrives.
import { Image } from '@dashforge/tw';
// Responsive, aspect-locked
<Image src={photo} alt="Product" aspectRatio={4 / 3} rounded="md" />
// Fixed thumbnail
<Image src={avatar} alt="Jane Doe" width={48} height={48} rounded="full" />import { Image } from '@dashforge/tw';
<Image
src="https://picsum.photos/seed/dashforge/800/450"
alt="A calm mountain lake at dawn"
aspectRatio={16 / 9}
rounded="lg"
sx="w-full max-w-md"
/>An aspect-locked, rounded image. Because aspectRatio reserves the box, the surrounding layout doesn't shift while the image loads — and a skeleton fills the space until it does.
import { Image, Stack, Typography } from '@dashforge/tw';
const SRC = 'https://picsum.photos/seed/dashforge-wide/1200/500';
<Stack direction="row" gap={3} align="start" sx="w-full">
<Stack gap={1} sx="flex-1">
<Image src={SRC} alt="Wide landscape, cropped to a square" aspectRatio={1} fit="cover" rounded="md" />
<Typography variant="caption" color="muted" align="center">fit="cover" (crops)</Typography>
</Stack>
<Stack gap={1} sx="flex-1">
<Image src={SRC} alt="Wide landscape, letterboxed" aspectRatio={1} fit="contain" rounded="md" sx="bg-neutral-100" />
<Typography variant="caption" color="muted" align="center">fit="contain" (letterboxes)</Typography>
</Stack>
</Stack>fit maps to CSS object-fit. The default cover crops the image to fill the box; contain letterboxes it so the whole image is visible. Both need a reserved box (here a square aspectRatio={1}) to have an effect.


import { Image, Stack, Typography } from '@dashforge/tw';
<Stack direction="row" gap={3} align="start" sx="w-full">
<Stack gap={1} sx="flex-1">
<Image src="/this-image-does-not-exist.jpg" alt="Product photo" aspectRatio={1} rounded="md" />
<Typography variant="caption" color="muted" align="center">default fallback</Typography>
</Stack>
<Stack gap={1} sx="flex-1">
<Image
src="/also-missing.jpg"
alt="Product photo"
aspectRatio={1}
rounded="md"
fallback={<Typography variant="caption" color="muted">Image unavailable</Typography>}
/>
<Typography variant="caption" color="muted" align="center">custom fallback</Typography>
</Stack>
</Stack>When the source fails to load, Image replaces the broken-image glyph with a muted placeholder. Pass your own via fallback.
Image is Option-C themable — set global defaults for fit and rounded (and slot overrides) via theme.components.Image:
patchTheme({
components: {
Image: { defaults: { rounded: 'md', fit: 'cover' } },
},
});Precedence, lowest to highest: the recipe's defaultVariants → theme.components.Image.defaults → the instance prop → sx / slotProps.
| Prop | Type | Default | Description |
|---|---|---|---|
alt | string | — | Alternative text (accessibility). Describe what the image conveys; use alt="" for purely decorative images so assistive tech skips it. |
src | string | — | Image source URL. Same-origin / data: keeps it inside your boundary; an external host is egress. |
access | AccessRequirement | — | RBAC access requirement. When the current user is unauthorized: onUnauthorized: 'hide' → the image does not render; 'disable' / 'readonly' → it renders dimmed and non-interactive (an image has no true disabled state). Resolved against the nearest RbacProvider. |
aspectRatio | number | string | — | Locks the box shape before load to prevent layout shift — a number (16 / 9, 1) or a CSS aspect-ratio string ('16 / 9'). When set, the image fills the box via object-fit. |
fallback | ReactNode | — | Content shown when the image fails to load. Defaults to a muted surface with a broken-image glyph. |
fit | 'none' | 'cover' | 'contain' | 'fill' | 'scale-down' | 'cover' | How the image fills its box — CSS object-fit. |
loading | 'lazy' | 'eager' | 'lazy' | Native lazy/eager loading. Lazy defers off-screen images. |
rounded | 'md' | 'sm' | 'lg' | 'none' | 'full' | 'none' | Corner radius token, clipped via overflow-hidden. |
showSkeleton | boolean | true | Show a <Skeleton> while the image loads. Requires a reserved box (aspectRatio, or both width and height) — without one there is no space to paint it, so it is skipped. |
slotProps | ImageSlotProps | — | Per-slot overrides. |
sx | string | — | Root-level Tailwind override (wins via tailwind-merge). |
visibleWhen | (engine: Engine) => boolean | — | Reactive visibility predicate evaluated against the form engine — the image renders only when it returns true (e.g. show a preview once a file field has a value). No-op outside a <DashForm>. |
...rest | Omit<ImgHTMLAttributes<HTMLImageElement>, 'src' | 'alt' | 'className'> | — | Additional native attributes forwarded via Omit<ImgHTMLAttributes<HTMLImageElement>, 'src' | 'alt' | 'className'>. |
aspectRatio, or both width and height. Without one, the image still renders, but the layout shifts as it loads and the skeleton is skipped.Image is declarative: it points the browser at whatever src you give it. An external host means the browser fetches from that host (egress) — self-host the asset (same-origin, your CDN, or a data: URI) to keep it inside your boundary.alt is required. Describe what the image conveys; use alt="" for purely decorative images so assistive tech skips them.<AspectRatio> + a plain <img> is enough. Image is the batteries-included version.