ImageViewer
Standalone image viewer that renders an ImageSource (URL, base64, or raw bytes) and
layers optional chrome on top: a bordered frame, a toolbar with rotate / flip / zoom / fullscreen,
download buttons, and crop interactions. For column-bound editing (drop zone, upload, validation)
use ImageEdit instead.
Bare
Default — a plain <img> sized to its parent. No chrome.
Framed + Downloadable
Adds a border, padding, and a corner download button. This is the read-only preview shape ImageEdit uses.

Toolbar
Adds rotate (CCW/CW), flip (H/V), zoom in/out, reset, fullscreen, and download buttons. The image responds to wheel-zoom (anchored at the cursor) and, when zoomed past 1×, drag-to-pan.

Toolbar + Croppable
Enables an aspect-ratio picker and a Crop button. Left-drag on the image draws a dashed selection
rectangle (constrained to the chosen aspect). Pressing Crop rasterizes the selection to PNG via
an offscreen canvas and fires OnCropApplied; the viewer then displays the cropped
result. Reset reverts to the original source.

ImageViewer Class
Parameters
Name | Type | Default | Description |
|---|---|---|---|
Alt | string? | Accessible alt text. Defaults to ImageViewer.FileName if unset. | |
Croppable | bool | False | When true (and ImageViewer.Toolbar is also true), enables crop interactions: the toolbar gains an aspect-ratio picker and a Crop button; left-drag on the image draws a dashed selection rectangle. Pressing Crop rasterizes the selection to PNG and fires ImageViewer.OnCropApplied; the viewer then displays the cropped result. Reset reverts to the original ImageViewer.Source. |
Downloadable | bool | False | When true, exposes a download UX:
|
Fallback | RenderFragment? | Rendered when ImageViewer.Source is null. | |
FileName | string? | File name used when downloading and as the default ImageViewer.Alt text. Falls back to the source's own ImageSource.FileName, otherwise | |
Framed | bool | False | When true, wraps the image in a Fluent-tokened bordered frame matching the read-only preview style used by |
IsDirty | bool | False | Current dirty state — true when the displayed image differs from the baseline (rotation, flip, or applied crop). Pure view state (zoom, pan) doesn't toggle dirty on its own. |
OutputFormat | ImageOutputFormat | Auto | Output format for the rasterised crop result (and the committed image bytes from ImageViewer.CommitAsync). Defaults to ImageOutputFormat.Auto — the format is derived from the source file's extension (JPEG inputs stay JPEG, everything else emits PNG). Pin to ImageOutputFormat.Png for lossless writes regardless of source, or ImageOutputFormat.Jpeg for size-sensitive photo flows. |
OutputQuality | double | 0.9 | JPEG quality in ImageViewer.OutputFormat is ImageOutputFormat.Png (PNG is lossless and has no quality knob). Defaults to |
Source | ImageSource? | Image data to render. When null, ImageViewer.Fallback is rendered instead (or nothing if no fallback is supplied). | |
Toolbar | bool | False | When true, renders a toolbar above the image with rotate / flip / zoom / reset / fullscreen controls. Implies a wrapper element regardless of ImageViewer.Framed. Drag-to-pan on the image becomes available when zoomed past 1×. |
AltImageViewer.FileName if unset.CroppableImageViewer.Toolbar is also true), enables crop interactions: the toolbar gains an aspect-ratio picker and a Crop button; left-drag on the image draws a dashed selection rectangle. Pressing Crop rasterizes the selection to PNG and fires ImageViewer.OnCropApplied; the viewer then displays the cropped result. Reset reverts to the original ImageViewer.Source. Downloadable- With
ImageViewer.Toolbar, adds a download button to the toolbar. - With
ImageViewer.Framed(no toolbar), renders an overlay button in the corner. - Without either, makes the image itself click-to-download.
FallbackImageViewer.Source is null.FileNameImageViewer.Alt text. Falls back to the source's own ImageSource.FileName, otherwise FramedIsDirtyOutputFormatImageViewer.CommitAsync). Defaults to ImageOutputFormat.Auto — the format is derived from the source file's extension (JPEG inputs stay JPEG, everything else emits PNG). Pin to ImageOutputFormat.Png for lossless writes regardless of source, or ImageOutputFormat.Jpeg for size-sensitive photo flows.OutputQualityImageViewer.OutputFormat is ImageOutputFormat.Png (PNG is lossless and has no quality knob). Defaults to SourceImageViewer.Fallback is rendered instead (or nothing if no fallback is supplied).ToolbarImageViewer.Framed. Drag-to-pan on the image becomes available when zoomed past 1×.Events
Name | Type | Description |
|---|---|---|
OnCancel | EventCallback | When set together with ImageViewer.Toolbar, renders a red prohibited- circle button at the right end of the toolbar. Always enabled. Consumers typically use this to exit edit mode and discard in-progress transforms — the viewer doesn't auto-revert anything on its own; remount it (e.g. via ImageViewer.Source to wipe its internal state. |
OnClick | EventCallback<MouseEventArgs> | Click handler on the |
OnCropApplied | EventCallback<CropResult> | Fires when the user commits a crop. Receives the rasterized PNG payload. |
OnDirtyChange | EventCallback<bool> | Fires whenever ImageViewer.IsDirty changes. Drives a parent's Save-button enabled state without polling. |
OnSave | EventCallback | When set together with ImageViewer.Toolbar, renders a green checkmark button at the right end of the toolbar. The button is disabled while the viewer is clean — there's nothing to save. Click handler is fire- and-forget; the consumer typically calls ImageViewer.CommitAsync from here to bake the displayed image to bytes for persistence. Designed for the |
OnCancelImageViewer.Toolbar, renders a red prohibited- circle button at the right end of the toolbar. Always enabled. Consumers typically use this to exit edit mode and discard in-progress transforms — the viewer doesn't auto-revert anything on its own; remount it (e.g. via ImageViewer.Source to wipe its internal state.OnClickOnCropAppliedOnDirtyChangeImageViewer.IsDirty changes. Drives a parent's Save-button enabled state without polling.OnSaveImageViewer.Toolbar, renders a green checkmark button at the right end of the toolbar. The button is disabled while the viewer is clean — there's nothing to save. Click handler is fire- and-forget; the consumer typically calls ImageViewer.CommitAsync from here to bake the displayed image to bytes for persistence. Designed for the Methods
Name | Parameters | Type | Description |
|---|---|---|---|
CommitAsync | Task<CropResult> | Bakes the currently-displayed image — including any rotation and flips — into a PNG at source resolution. Returns null when the viewer is clean (nothing to save) or before the JS handle has attached. Any in-progress crop selection that hasn't been committed via the Crop button is intentionally ignored; click Crop first if you want it baked in. | |
HandleFullscreenChange | bool isFullscreen | Task | Document-level |
HandleKeyDown | string key bool shiftKey | Task | JS-driven keydown callback for the edit-mode shortcuts. Only fires for the keys we claim (the JS shim filters by key + modifier state), so we don't have to gate this method itself.
|
HandleWheel | double deltaY double cursorRelX double cursorRelY | Task | Wheel-zoom callback — JS forwards each scroll tick (deltaY) plus the cursor's offset from the container center. Anchored zoom math follows React's: pan_new = (1 − r) · cursorRel + r · pan_prev where r = zoom_new / zoom_prev. Keeps the cursor over the same image-local point across the zoom step. |
ResetDirtyAsync | Task | Marks the viewer clean without changing the displayed image. Use after a successful save: the cropped bytes are now the saved version, so future changes should be measured against this state. To revert the displayed image to the original ImageViewer.Source AND clear dirty, use the Reset toolbar button or update ImageViewer.Source. | |
RevertCrop | void | Drops the local crop overlay only — reverts to the original ImageViewer.Source. Rotation and flips stay where the user has them, so if those changed since the last save, ImageViewer.IsDirty may remain true. Use the toolbar's Reset button (or updating ImageViewer.Source) for a full revert. |
CommitAsyncHandleFullscreenChangeHandleKeyDownbool shiftKey
-
Escape — invokeImageViewer.OnCancel(if wired). -
Enter — commit the selection as a crop if there is one ≥ 5×5; otherwise invokeImageViewer.OnSaveif dirty. -
+ /= /- /_ — zoom in / out. -
0 — reset zoom + pan to identity. -
Delete /Backspace — clear the selection. -
Arrow keys — nudge the selection 1 px (10 px with Shift).
HandleWheeldouble cursorRelX
double cursorRelY
ResetDirtyAsyncImageViewer.Source AND clear dirty, use the Reset toolbar button or update ImageViewer.Source.RevertCropImageViewer.Source. Rotation and flips stay where the user has them, so if those changed since the last save, ImageViewer.IsDirty may remain true. Use the toolbar's Reset button (or updating ImageViewer.Source) for a full revert.