Skip to content
Devix Open Source

Customize

Styling and theming

import '@devix-labs/image-cropper/styles.css';        // plain
import '@devix-labs/image-cropper/styles.layer.css';  // the same, inside @layer devix

Variables

.dxi {
  --dxi-radius: 12px;
  --dxi-font-size: 13px;
  --dxi-gap: 10px;
  --dxi-stage-height: 320px;
  --dxi-handle: 12px;
  --dxi-tool: 30px;

  --dxi-bg: #fff;
  --dxi-text: #18181b;
  --dxi-muted: #71717a;
  --dxi-border: #e4e4e7;
  --dxi-surface: #fafafa;      /* the drop zone */
  --dxi-stage-bg: #f4f4f5;     /* behind the picture */
  --dxi-accent: #ea4b71;       /* handles, focus rings */
  --dxi-shade: rgb(17 17 28 / 0.55);   /* over everything outside the crop */
  --dxi-danger: #dc2626;
  --dxi-checks: repeating-conic-gradient(#e4e4e7 0% 25%, #fff 0% 50%) 50% / 16px 16px;
}

A taller stage for landscape work, or a shorter one in a sidebar:

.dxi { --dxi-stage-height: 460px; }

Dark

theme: 'auto' reads the page — Tailwind's .dark, data-theme, Bootstrap's data-bs-theme, or simply the background behind the widget — and re-reads it when a theme toggle flips.

Your classes

createImageCropper(input, {
  classNames: {
    empty: 'rounded-2xl border-2 border-dashed',
    stage: 'rounded-2xl',
    handle: 'ring-2 ring-white',
    tool: 'rounded-full',
  },
});

Parts: root, empty, stage, image, selection, handle, grid, tools, tool, zoom, meta, actions, button, preview, error, aspects, aspectButton.

State

Selector When
.dxi--empty No picture yet.
.dxi--over A file is being dragged over it.
.dxi--dragging The crop or the picture is being moved.
.dxi--fixed In avatar mode.
.dxi-selection--round The crop is an ellipse.
.dxi-aspect.is-chosen The shape currently in use.

Three recipes

shadcn/ui

.dxi {
  --dxi-bg: hsl(var(--card));
  --dxi-surface: hsl(var(--muted));
  --dxi-stage-bg: hsl(var(--muted));
  --dxi-text: hsl(var(--card-foreground));
  --dxi-muted: hsl(var(--muted-foreground));
  --dxi-border: hsl(var(--border));
  --dxi-accent: hsl(var(--primary));
  --dxi-radius: var(--radius);
}

Bootstrap 5

.dxi {
  --dxi-bg: var(--bs-body-bg);
  --dxi-surface: var(--bs-tertiary-bg);
  --dxi-stage-bg: var(--bs-secondary-bg);
  --dxi-text: var(--bs-body-color);
  --dxi-muted: var(--bs-secondary-color);
  --dxi-border: var(--bs-border-color);
  --dxi-accent: var(--bs-primary);
  --dxi-radius: var(--bs-border-radius-lg);
}

Material 3

.dxi {
  --dxi-bg: var(--md-sys-color-surface);
  --dxi-surface: var(--md-sys-color-surface-container);
  --dxi-stage-bg: var(--md-sys-color-surface-container-low);
  --dxi-text: var(--md-sys-color-on-surface);
  --dxi-muted: var(--md-sys-color-on-surface-variant);
  --dxi-border: var(--md-sys-color-outline-variant);
  --dxi-accent: var(--md-sys-color-primary);
  --dxi-radius: 16px;
  --dxi-handle: 16px;
}

An avatar that looks like an avatar

createImageCropper(input, { mode: 'fixed', aspect: 1, round: true });
.dxi { --dxi-stage-height: 260px; --dxi-shade: rgb(17 17 28 / 0.7); }
.dxi-selection { outline-width: 2px; }

The heavier shade and the thicker outline make the circle read as the subject rather than as a rectangle with a hole in it.

Updated 15 Sep 2026