Skip to content

External Libraries Assessment

This document evaluates existing React component libraries and their fit for implementing the image upload UI components. The assessment covers the project’s established stack — ShadCN/ui (with Radix UI primitives), Tailwind CSS, and AG Grid — plus specialized image libraries needed to fill gaps.

For the component specifications that these libraries support, see UI Components.

ShadCN/ui provides copy-paste, project-owned React components built on Radix UI headless primitives and styled with Tailwind CSS. The project already uses ShadCN stock primitives in canary/primitives/ with custom Arda atoms wrapping them. Radix primitives provide WAI-ARIA-compliant accessibility (focus trapping, keyboard navigation, screen reader announcements) out of the box.

Our ComponentShadCN/Radix PrimitivesCoverageGaps
ImageCellDisplayAvatar (image + fallback with delayMs), AspectRatio, Skeleton (loading shimmer), TooltipHighError badge overlay is custom; shimmer animation needs a custom @keyframes in @theme or use of Skeleton component
ImageInspectorOverlayDialog (modal, portal, overlay, focus trap, Escape/click-outside dismiss)Very HighImage zoom/pan gestures within the viewer are not provided
ImageDropZoneVisuallyHidden (accessible hidden file input), Label, Input (URL text field), Button (file picker trigger)LowDrag-and-drop, clipboard paste, camera capture, and input classification are entirely custom. Radix has no FileUpload primitive (open request: radix-ui/primitives#1327). See react-dropzone below
ImagePreviewEditorSlider (zoom control with keyboard), Toolbar (roving tabindex for edit controls), Toggle / ToggleGroup (crop/pan mode), AspectRatio (preview container), Tooltip (toolbar button labels)MediumImage cropping, rotation, and canvas manipulation require a dedicated library. See react-easy-crop below
ImageComparisonLayoutTabs (mobile tabbed view with keyboard nav, ARIA roles)MediumDesktop side-by-side layout is plain CSS flexbox. Responsive switching is a media query concern, not a component library concern
CopyrightAcknowledgmentCheckbox (controlled, required, hidden native input), LabelVery HighTimestamp logging is application logic only
ImageUploadDialogDialog (modal mode: focus trap, portal, overlay), Progress (upload progress bar), AlertDialog (Warn state discard confirmation)MediumState machine orchestration (useReducer or XState), validation logic, presigned POST workflow, and auto-compression are all custom
ImageRemoveConfirmationAlertDialog (purpose-built for confirm/cancel interruptions: Action + Cancel buttons, no click-outside dismiss, ARIA alert dialog pattern)CompleteNone — AlertDialog covers this entirely
EntityImageFieldLabel (form field), Avatar (current image display), Tooltip (action button labels), Dialog (composed via ImageUploadDialog), AlertDialog (composed via ImageRemoveConfirmation)MediumForm field layout, integration with form libraries (react-hook-form), and field-level validation are custom

When to use Radix directly vs. ShadCN/ui: For ImageInspectorOverlay, CopyrightAcknowledgment, and ImageRemoveConfirmation, the ShadCN wrapper adds only default Tailwind styling over the Radix primitive — using Radix directly is viable if custom styling is preferred. For more complex compositions (ImageUploadDialog, EntityImageField), ShadCN’s pre-styled Dialog, Tabs, and Button components save time without adding behavioral value beyond Radix.

Community extensions worth evaluating:


The project uses Tailwind CSS v4 with CSS-first configuration (@import 'tailwindcss', @theme inline) and tw-animate-css for entry/exit animations. The cn() utility (clsx + tailwind-merge) is the standard class-merging pattern.

Tailwind covers 100% of the visual styling needs across all components and 0% of the behavioral logic (drag-and-drop events, image manipulation, state machines).

Our ComponentKey Tailwind UtilitiesNotes
ImageCellDisplayaspect-square, object-cover, object-center, animate-[shimmer] (custom @keyframes in @theme), absolute (error badge)Shimmer requires a custom keyframe definition
ImageInspectorOverlayfixed inset-0 z-50 bg-black/80, max-w-full max-h-[90vh] object-contain, tw-animate-css fade/zoomFully covered
ImageDropZoneborder-2 border-dashed rounded-lg, dynamic cn() toggle for drag-over highlight (no native drag-over: variant)Drag-over state requires JS class toggle
ImagePreviewEditoraspect-square overflow-hidden relative, transition-transform for zoom/rotate feedbackCrop overlay and canvas ops are outside Tailwind scope
ImageComparisonLayouthidden md:flex md:flex-row md:gap-4 (desktop), flex flex-col md:hidden (mobile tabs)md: breakpoint handles responsive switching
CopyrightAcknowledgmentflex items-start gap-3, data-[disabled]:opacity-50Simplest component
ImageUploadDialogborder rounded-lg p-4 (inline), Dialog overlay styles (modal), text-destructive (errors), tw-animate-css transitionsState-dependent content is React logic
ImageRemoveConfirmationbg-destructive text-destructive-foreground (action button)Fully covered via AlertDialog styling
EntityImageFieldrelative group rounded-lg, group-hover:opacity-100 transition-opacity (hover affordances), disabled:pointer-events-noneFully covered

No additional Tailwind plugins needed. aspect-square and aspect-[1/1] are native in v4. @tailwindcss/forms is not needed (ShadCN primitives handle form styling). @tailwindcss/typography is not needed (no prose rendering).


AG Grid provides the data grid in which ImageCellDisplay renders and from which ImageInspectorOverlay and ImageUploadDialog (modal mode) are triggered. All required features are available in AG Grid Community (MIT license).

Custom Cell Renderer (ImageCellDisplay):

  • Register via cellRenderer: ImageCellDisplay in the column definition
  • Pass click handlers via cellRendererParams
  • deferRender: true defers thumbnail rendering until scrolling stops, showing a loadingCellRenderer (skeleton shimmer) meanwhile
  • React.lazy() is natively supported for cell renderers
  • DOM virtualization (default rowBuffer: 10) means only visible rows render — use <img loading="lazy">, fixed dimensions, and React.memo for performance in large grids

Click vs. double-click distinction (inspect vs. edit):

  • Set suppressClickEdit: true on the image column
  • Use a timer-based debounce: onCellClicked sets a 250ms timeout; onCellDoubleClicked clears it and opens the edit dialog
  • Alternatively, handle click/double-click inside the cell renderer itself on the thumbnail element
  • Enter key: use onCellKeyDown to detect Enter and trigger the edit dialog

Modal overlay (preventing grid interaction while ImageUploadDialog is open):

  • AG Grid’s built-in overlays do not block pointer events (known issue ag-grid#1755)
  • Recommended: external overlay <div> over the grid container with pointer-events: all, toggled when the modal opens/closes
  • Focus trapping (from Radix Dialog) prevents Tab from reaching grid cells

Cell editor API — using a cell editor for the upload dialog is not recommended for the main upload workflow. Cell editors are tightly coupled to the grid’s value-in/value-out lifecycle, which does not map cleanly to an async upload workflow with multiple interaction states. However, an ImageCellEditor wrapper is provided for AG Grid API consistency — it triggers the external ImageUploadDialog and updates the row via api.getRowNode(id).setDataValue() on confirm.


These libraries fill the gaps that ShadCN/Radix/Tailwind/AG Grid do not cover: image cropping, drag-and-drop file handling, and client-side compression.

Recommended for ImagePreviewEditor.

Version5.5.6
LicenseMIT
npm weekly downloads~888K
Bundle size7.1 KB gzip / 23.8 KB min
TypeScriptBuilt-in types
Last publishedNov 2025

Fit assessment:

RequirementSupportNotes
Locked aspect ratio (1:1)Built-inaspect prop — set to 1
ZoomBuilt-inzoom, minZoom, maxZoom, onZoomChange — pair with Radix Slider
RotateBuilt-inrotation + onRotationChange — declarative, controlled
90-degree incrementsTrivialButton that increments rotation state by 90
PanBuilt-inDrag to reposition within crop area
ResetTrivialReset state values for crop, zoom, rotation
File + URL sourcesBuilt-inAccepts image URL strings, base64, or blob URLs
Touch/mobileBuilt-inTouch gestures for pan and pinch-to-zoom

Gap: No built-in “get cropped image” output — requires a canvas helper function to produce the final cropped Blob. This is a well-documented pattern (~15 lines of code).

Alternatives considered:

  • react-image-crop (825K downloads, 4.3 KB gzip) — no built-in zoom or rotation; poor fit
  • react-cropper (215K downloads) — wraps Cropper.js v1; imperative API, last published Apr 2023 (stale)
  • Cropper.js v2 (794K downloads) — powerful but no official React wrapper; requires Web Components integration

Recommended as the foundation for ImageDropZone.

Version15.0.0
LicenseMIT
npm weekly downloads~5.4M
Bundle size16.3 KB gzip / 59.9 KB min
TypeScriptBuilt-in types
Last publishedFeb 2026

Fit assessment:

RequirementSupportNotes
Drag-and-dropBuilt-inVisual feedback via isDragActive state
File picker (click)Built-inopen() function or click-to-open
File type validationBuilt-inaccept prop with MIME types
File size limitsBuilt-inmaxSize, minSize props
Clipboard pasteNot built-inAdd custom paste event listener (~15 lines); extract from clipboardData.files or parse HTML for image URLs
URL text inputNot built-inStandard <Input> component alongside the drop zone
Camera captureNot built-in<input type="file" accept="image/*" capture="environment"> for mobile
Headless/unstyledYesFull control over UI — compose with ShadCN/Tailwind

Alternatives considered:

  • FilePond / react-filepond (56K downloads) — built-in paste, URL, camera; but opinionated UI conflicts with custom design systems; plugin-heavy architecture
  • Uppy / @uppy/react (206K downloads) — most feature-complete (Dashboard, Webcam, ImageEditor plugins); massive overkill for a focused drop zone; heavy bundle cost

Recommended for client-side auto-compression in ImageUploadDialog.

Version2.0.2
LicenseMIT
npm weekly downloads~533K
Bundle size19.6 KB gzip / 51.7 KB min
TypeScriptBuilt-in types
Last publishedMar 2023

Fit assessment:

RequirementSupportNotes
JPEG quality (85%)Built-inQuality control option
Max dimension (2048px)Built-inmaxWidthOrHeight option
Non-blockingBuilt-inRuns in Web Worker
EXIF orientation fixBuilt-inAuto-corrects rotated photos
Returns File/BlobBuilt-inDirect input to presigned POST

Gap: No HEIC support — cannot read HEIC files. Requires a separate conversion step if HEIC is accepted.


Required only if HEIC format support is confirmed in scope.

Version0.0.4
LicenseMIT
npm weekly downloads~230–500K
Bundle size~1.15 MB gzip (uses libheif WASM)
TypeScriptNo built-in types
Last publishedMar 2023

Must be lazy-loaded. Detect HEIC by file extension / MIME type, dynamically import('heic2any') only when needed. The 1.15 MB gzip cost is acceptable only as a conditional, on-demand load — never in the main bundle.

Pipeline: detect HEIC → convert with heic2any → compress with browser-image-compression → proceed to preview.


Potentially useful for ImageComparisonLayout if an interactive slider comparison UX is desired.

Version4.0.0
LicenseMIT
npm weekly downloads~55K
Bundle size3.4 KB gzip / 9.2 KB min
TypeScriptBuilt-in types
Last publishedMar 2026

However, the current ImageComparisonLayout spec calls for side-by-side (desktop) or tabbed (mobile) views — not a slider overlay. This is a layout concern addressable with CSS flexbox + ShadCN Tabs, adding zero dependency weight. The library is worth revisiting if the UX evolves toward an interactive slider comparison.


ComponentShadCN/RadixTailwindAG GridExternal LibraryCustom Code
ImageCellDisplayAvatar, Skeleton, AspectRatioStylingCell renderer, deferRenderError badge
ImageInspectorOverlayDialogStylingEvent trigger
ImageDropZoneVisuallyHidden, Label, Input, ButtonStyling (no drag-over:)react-dropzoneClipboard paste, URL input, camera, input classification
ImagePreviewEditorSlider, Toolbar, ToggleStylingreact-easy-cropCanvas crop output helper
ImageComparisonLayoutTabsResponsive layoutResponsive switching logic
CopyrightAcknowledgmentCheckbox, LabelStylingTimestamp logging
ImageUploadDialogDialog, Progress, AlertDialogStyling, transitionsbrowser-image-compression, heic2any (lazy)State machine, validation, presigned POST
ImageRemoveConfirmationAlertDialogStyling
EntityImageFieldLabel, Avatar, TooltipStylingForm integration
LibraryPurposeBundle Impact (gzip)Covers
react-easy-cropImage crop/zoom/rotate/pan7.1 KBImagePreviewEditor
react-dropzoneDrag-and-drop + file picker16.3 KBImageDropZone (foundation)
browser-image-compressionClient-side auto-compression19.6 KBImageUploadDialog (validation pipeline)
heic2anyHEIC-to-JPEG conversion~1.15 MB (lazy-loaded only)ImageUploadDialog (HEIC support, if confirmed)

Total baseline bundle impact: ~43 KB gzip (excluding heic2any, which is lazy-loaded on demand).


Copyright: (c) Arda Systems 2025-2026, All rights reserved