Documentation
Image and Icon Components
Display images from URLs, resources, or bitmaps, plus Material Design icons.
Image Component
From URL
<Image src="https://example.com/photo.jpg" /> From Bitmap
val bitmap = loadBitmap()
<Image bitmap={bitmap} /> Size Control
<Image src={url} w={200} h={200} />
<Image src={url} fillMaxWidth h={300} /> Aspect Ratio
Maintain aspect ratio while fitting container:
<Image src={url} aspectRatio={16.0/9.0} />
<Image src={url} aspectRatio={1.0} /> // Square Content Scale
Control how images fit within their bounds:
<Image src={url} fit="crop" /> // Fill, cropping if needed
<Image src={url} fit="fit" /> // Fit inside, may have empty space
<Image src={url} fit="fillWidth" />
<Image src={url} fit="fillHeight" /> | Fit Value | Effect |
|---|---|
"crop" | Fill bounds, crop overflow |
"fit" | Fit inside bounds, maintain aspect ratio |
"fillWidth" | Fill width, scale height proportionally |
"fillHeight" | Fill height, scale width proportionally |
"fillBounds" | Fill both dimensions (may distort) |
Corner Radius
<Image src={url} cornerRadius={8} />
<Image src={url} cornerRadius={100} /> // Circle Circular Images
<Image src={avatar} w={48} h={48} cornerRadius={24} /> Overlay Gradient
Add gradient overlays for better text contrast:
<Image
src={poster}
overlay="v-gradient(transparent 60%, #000)"
h={400}
/> <Box>
<Image src={photo} />
<Box
background="v-gradient(transparent 60%, #000)"
fillMaxSize
/>
<Text color="#FFF" p={16}>Caption</Text>
</Box> Loading State
<Image
src={url}
placeholder={R.drawable.placeholder}
error={R.drawable.error}
/> Clickable Images
Images support onClick like any component:
<Image
src={product.image}
onClick={() => $navigate("/product/${product.id}")}
/> UltraHDR
A JPEG carrying a gain map holds two pictures: a normal one, and a map of how much brighter each pixel goes on a display that can show it. Whitehall decodes it three ways.
<Image src="/photo.jpg" hdr /> // base × gain map — the point of the format
<Image src="/photo.jpg" sdr /> // the base picture alone
<Image src="/photo.jpg" gainmap /> // the gain map itself, as greyscale Or bind it, when the mode is something the app decides:
var mode = "hdr"
<Image src="/photo.jpg" mode={mode} /> // "hdr" | "sdr" | "gainmap" The three shorthands and mode= are one prop wearing four spellings, so writing
two of them is a build error rather than a silent winner:
<Image src="/photo.jpg" hdr sdr /> // error It is an ordinary
<Image>. The disk cache, the network path, fit=, onClick, corner radius, overlays and morph bindings all work alongside it — decoding a gain map
changes what the pixels are, not what the component is.Gain map decoding needs API 34. Below that the platform has no
Gainmap to read.Icon Component
An icon names the set it comes from, and the set is declared in whitehall.toml —
see Icons for the whole of it.
<Icon name="lucide:heart" />
<Icon name="ic:baseline-settings" /> Size
<Icon name="lucide:star" size={16} />
<Icon name="lucide:star" size={24} />
<Icon name="lucide:star" size={48} /> Color
<Icon name="lucide:heart" color="primary" />
<Icon name="ic:baseline-error" color="error" />
<Icon name="lucide:check" color="#00FF00" /> Rotation
<Icon name="ic:baseline-arrow-forward" rotate={90} /> // Down
<Icon name="ic:baseline-arrow-forward" rotate={180} /> // Back
<Icon name="ic:baseline-arrow-forward" rotate={270} /> // Up Common Patterns
Avatar
<Image
src={user.avatar}
w={40} h={40}
cornerRadius={20}
/> Hero Image
<Box>
<Image
src={article.cover}
fillMaxWidth
h={250}
fit="crop"
/>
<Box
background="v-gradient(transparent 50%, #000)"
fillMaxSize
/>
<Column
fillMaxSize
verticalArrangement="end"
p={20}
>
<Text fontSize={28} fontWeight="bold" color="#FFF">
{article.title}
</Text>
</Column>
</Box> Product Card
<Card onClick={() => $navigate("/product/{product.id}")}>
<Image
src={product.image}
fillMaxWidth
aspectRatio={1.0}
fit="crop"
/>
<Column p={12} gap={4}>
<Text fontSize={14} maxLines={2}>{product.name}</Text>
<Row gap={4} verticalAlignment="center">
<Icon name="lucide:star" size={16} color="primary" />
<Text fontSize={12}>{product.rating}</Text>
</Row>
<Text fontSize={16} fontWeight="bold">${product.price}</Text>
</Column>
</Card> Icon with Label
<Column horizontalAlignment="center" gap={4}>
<Icon name="lucide:shopping-cart" size={32} color="primary" />
<Text fontSize={12}>Cart</Text>
</Column> Icon Button
<Button onClick={toggleFavorite}>
@if (isFavorite) {
<Icon name="ic:baseline-favorite" color="error" />
} @else {
<Icon name="ic:baseline-favorite-border" color="secondary" />
}
</Button> Rating Stars
val rating = 4.5
<Row gap={4}>
@for (i in 1..5) {
@if (i <= rating) {
<Icon name="ic:baseline-star" color="primary" size={20} />
} @else {
<Icon name="ic:baseline-star-border" color="primary" size={20} />
}
}
<Text fontSize={14} color="secondary">({reviewCount})</Text>
</Row> Image Gallery Grid
<LazyVerticalGrid columns={3} gap={2}>
@for (photo in photos) {
<Image
src={photo.thumbnail}
aspectRatio={1.0}
fit="crop"
key={photo.id}
onClick={() => $navigate("/photo/{photo.id}")}
transition:crossfade="photo-{photo.id}"
/>
}
</LazyVerticalGrid> Background Image
<Box fillMaxSize>
<Image
src={backgroundUrl}
fillMaxSize
fit="crop"
/>
<Box
background="v-gradient(transparent, #000000CC)"
fillMaxSize
/>
<Column fillMaxSize p={20} verticalArrangement="center">
<Text fontSize={32} color="#FFF" fontWeight="bold">
Welcome
</Text>
</Column>
</Box> Profile Header
<Column>
<Box h={200}>
<Image src={user.coverPhoto} fillMaxSize fit="crop" />
</Box>
<Row p={16} gap={16}>
<Image
src={user.avatar}
w={80} h={80}
cornerRadius={40}
mt={-40} // Overlap cover photo
/>
<Column weight={1}>
<Text fontSize={24} fontWeight="bold">{user.name}</Text>
<Text fontSize={14} color="secondary">{user.bio}</Text>
</Column>
</Row>
</Column> Prop Reference
Image Props
| Prop | Type | Description |
|---|---|---|
src | String | Image URL |
bitmap | Bitmap | Bitmap object |
fit | String | Content scale mode |
aspectRatio | Number | Width/height ratio |
cornerRadius | Number | Corner radius in dp |
overlay | String | Gradient overlay |
placeholder | Resource | Loading placeholder |
error | Resource | Error fallback |
Icon Props
| Prop | Type | Description |
|---|---|---|
name | String | Icon name (kebab-case) |
size | Number | Icon size in dp |
color | String | Icon color |
tint | String | Alias for color |
rotate | Number | Rotation in degrees |
See Also
- Lazy Lists - Display image grids and galleries
- transition:crossfade - Shared element transitions with images
- Button Component - Icon buttons