Getting StartedComponentsDesign TokensThemingTheme SynthesizerFrameworksAccessibilityUtilitiesServer RenderingBrowser SupportContributingChangelog App ShellAspect GridAuth ShellCenterClusterContainerDashboard GridDockFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkSpeed DialStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCalloutCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSeparatorSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridData TableDescription ListDiffEvent CalendarGaugeJSON TreeKanbanKey ValueLevel MeterListMeterSparklineStatStepperTableTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputOTP InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuGuided TourHover CardInline MessageLoading OverlayModalNotification PanelPopoverProgressProgress ToastSheetSnackbarSpotlightToastTooltip
ARC UI ARC Radiant Components
v3.2 Docs Components Tokens Synthesizer
Getting StartedFrameworksServer Rendering Design TokensThemingTheme SynthesizerTypographyUtilities All ComponentsAccessibilityBrowser SupportChangelogContributingStats App ShellAspect GridAuth ShellCenterClusterContainerDashboard GridDockFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkSpeed DialStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCalloutCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSeparatorSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridData TableDescription ListDiffEvent CalendarGaugeJSON TreeKanbanKey ValueLevel MeterListMeterSparklineStatStepperTableTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputOTP InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuGuided TourHover CardInline MessageLoading OverlayModalNotification PanelPopoverProgressProgress ToastSheetSnackbarSpotlightToastTooltip

Image Cropper

Crop-before-upload control with a draggable, resizable crop rectangle, aspect-ratio locking, zoom, and canvas export at natural image resolution.

Components Image Cropper
input interactive beta
<arc-image-cropper>

Overview

ImageCropper renders an image letterboxed inside a fixed-height stage and overlays a crop rectangle that users drag to reposition and resize via eight handles (four corners, four edges). Everything outside the rectangle is darkened, and rule-of-thirds guides inside the rectangle aid composition. A labeled zoom slider below the stage scales the image around its center (1x-4x) while the crop rectangle stays put, letting users crop into fine detail. Setting the `aspect` prop (width/height, e.g. `1` for square avatars or `16/9`) locks the rectangle to that ratio through every drag, resize, and keyboard interaction. Leave it at `0` for free-form cropping. The rectangle is always clamped to the visible image and never shrinks below 32px. The component exposes three methods: `getCrop()` returns `{ x, y, width, height }` in natural image pixel coordinates (letterbox scale and zoom are accounted for precisely); `getCroppedBlob(type, quality)` and `getCroppedDataUrl(type, quality)` draw the crop to an offscreen canvas at natural resolution. Canvas export requires `src` to be same-origin or served with CORS headers — a cross-origin image taints the canvas and the methods throw a descriptive error. The `arc-crop-change` event fires with natural-pixel coordinates, debounced to animation frames during drags.

Guidelines

When to use

  • Set `aspect="1"` for avatar flows so the exported crop is always square
  • Use an object URL (`URL.createObjectURL(file)`) as `src` when cropping a just-picked file from `arc-file-upload`
  • Listen for `arc-crop-change` to show a live preview or persist crop coordinates
  • Call `getCroppedBlob()` at upload time to send the cropped region at natural resolution
  • Serve remote images from the same origin or with CORS headers so canvas export works

When not to use

  • Do not pass a cross-origin `src` without CORS headers if you need `getCroppedBlob()` / `getCroppedDataUrl()` — the canvas will be tainted and the methods throw
  • Do not read crop coordinates from the rectangle position on screen — always use `getCrop()`, which converts to natural image pixels
  • Do not set `height` smaller than ~160px — the crop rectangle and handles need room to operate
  • Do not use ImageCropper for simple display-only image framing — use CSS `object-fit` or an aspect-ratio container instead

Features

  • Draggable crop rectangle with 8 resize handles (corners + edges) and touch-friendly hit areas
  • Aspect-ratio locking via the `aspect` prop, enforced through drag, resize, zoom, and keyboard
  • Zoom slider (1x-4x) scales the image around its center under a stationary crop rectangle
  • Rule-of-thirds guide lines inside the crop rectangle for composition
  • Darkened overlay outside the crop area with the image letterboxed in a fixed-height stage
  • `getCrop()` returns natural image pixel coordinates; `getCroppedBlob()` / `getCroppedDataUrl()` export at full resolution
  • Full keyboard support: focus the rectangle, arrows move 2px, Shift+arrows resize from the bottom-right
  • Position and size announced to screen readers on keyup via a polite live region
  • Skeleton shimmer while the image loads; inline error state on load failure
  • Crop rectangle clamped to image bounds at all times with a 32px minimum size

Preview

Usage

This component requires JavaScript. No pure HTML/CSS version is available — use the Web Component directly or a framework wrapper.

<arc-image-cropper
  src="/uploads/avatar-original.jpg"
  aspect="1"
  height="320"
></arc-image-cropper>

<script>
  const cropper = document.querySelector('arc-image-cropper');

  cropper.addEventListener('arc-crop-change', (e) => {
    console.log('Crop (natural px):', e.detail);
  });

  // At upload time — natural-resolution export:
  async function upload() {
    const blob = await cropper.getCroppedBlob('image/jpeg', 0.9);
    const body = new FormData();
    body.append('avatar', blob, 'avatar.jpg');
    await fetch('/api/avatar', { method: 'POST', body });
  }
</script>
import { useRef } from 'react';
import { ImageCropper } from '@arclux/arc-ui-react';

function AvatarEditor({ src }: { src: string }) {
  const ref = useRef<HTMLElementTagNameMap['arc-image-cropper']>(null);

  const save = async () => {
    const blob = await ref.current!.getCroppedBlob('image/jpeg', 0.9);
    // ...upload blob
  };

  return (
    <>
      <ImageCropper
        ref={ref}
        src={src}
        aspect={1}
        height={320}
        onArcCropChange={(e) => console.log(e.detail)}
      />
      <button onClick={save}>Save avatar</button>
    </>
  );
}
<script setup>
import { ref } from 'vue';
import { ImageCropper } from '@arclux/arc-ui-vue';

const cropper = ref(null);

async function save() {
  const blob = await cropper.value?.getCroppedBlob('image/jpeg', 0.9);
  // ...upload blob
}
</script>

<template>
  <ImageCropper
    ref="cropper"
    src="/uploads/avatar-original.jpg"
    :aspect="1"
    :height="320"
    @arc-crop-change="(e) => console.log(e.detail)"
  />
  <button @click="save">Save avatar</button>
</template>
<script>
  import { ImageCropper } from '@arclux/arc-ui-svelte';

  let cropper;

  async function save() {
    const blob = await cropper.getCroppedBlob('image/jpeg', 0.9);
    // ...upload blob
  }
</script>

<ImageCropper
  bind:this={cropper}
  src="/uploads/avatar-original.jpg"
  aspect={1}
  height={320}
  on:arc-crop-change={(e) => console.log(e.detail)}
/>
<button on:click={save}>Save avatar</button>
import { Component, ElementRef, ViewChild } from '@angular/core';
import { ImageCropper } from '@arclux/arc-ui-angular';

@Component({
  imports: [ImageCropper],
  template: `
    <arc-image-cropper
      #cropper
      src="/uploads/avatar-original.jpg"
      [aspect]="1"
      [height]="320"
      (arcCropChange)="onCropChange($event)"
    ></arc-image-cropper>
    <button (click)="save()">Save avatar</button>
  `,
})
export class AvatarEditorComponent {
  @ViewChild('cropper', { read: ElementRef }) cropper!: ElementRef;

  onCropChange(e: CustomEvent) {
    console.log(e.detail);
  }

  async save() {
    const blob = await this.cropper.nativeElement.getCroppedBlob('image/jpeg', 0.9);
    // ...upload blob
  }
}

API

src string ''
Image URL, object URL, or data URL to crop. Must be same-origin or CORS-enabled for canvas export.
height number 320
Fixed stage height in pixels. The image is letterboxed to fit.
aspect number 0
Crop aspect ratio as width/height (e.g. `1`, `16/9`). `0` allows free-form cropping.
zoom number 1
Image zoom factor, clamped to 1-4. Scales the image around its center; also settable via the built-in slider.

Events

arc-crop-change
Fired when the crop changes (drag, resize, keyboard, zoom, stage resize). `event.detail` is `{ x, y, width, height }` in natural image pixels, debounced to animation frames.

See Also