Styling
The parts carry no colour. Paint them with the optional stylesheet, your own CSS or Tailwind.
Add the stylesheet
Import the stylesheet once. Bundle size lists its weight:
import "@zriz/react/styles.css";| Part | What the stylesheet sets |
|---|---|
| Viewport | overflow: clip and a grab cursor, not-allowed on it, the crop, the handles and the dial while Root is disabled |
| Mask | The scrim colour. It fades to 60% during a gesture |
| Area | A 1 px outline inside the crop and a two-tone ring on keyboard focus |
| Handle | A corner mark of two short lines or an edge bar, and a resize cursor. Corners hide with shape="circle" |
| Grid | White lines that fade in during a gesture, clipped to the circle on a round crop |
| Dial | 40 px height, sideways clip with a 48 px fade at each side, a grab cursor, centre line, tick colours, the zero dot |
It paints nothing on Zoom, the buttons or the aspect items. The demos on this site style those themselves. Viewport shows how to size the box.
Every rule sits in :where() inside @layer components.zriz. A class with no layer wins in any import order, and so does a class in your components or utilities layer, Tailwind included. A class in any other layer wins only when that layer is declared after the stylesheet. @zriz/core/styles.css is the same file for vanilla JS.
A page-wide transition cannot make the crop trail your finger. zriz sets transition-property inline to none on Area, DialTicks and ZoomThumb, and to opacity on Mask, so the mask keeps its fade. A transition of your own on those parts needs !important.
The first line of the file names properties, theme, base, components and utilities, so a stylesheet loaded first sets the order of those layers on your page. To keep your own order, and to let every layer of yours win, import it into a layer of its own:
@layer vendor, reset, base, components, utilities;
@import url("@zriz/react/styles.css") layer(vendor);Style a part by its state
className takes a string, or a function that gets the part's state:
<Cropper.Grid
className={(state) =>
state.grid === "fine" ? "opacity-50" : undefined
}
/>Return undefined from the function for no class. To swap the element itself, see composition.
Style with data attributes
Every part's outer element carries data-zriz-item with its part name in kebab case, such as aspect-item or dial-ticks. The ticks and grid lines inside carry none, so select them through their part, as dial shows. Root also marks the gesture in progress with data-rotating, data-panning, data-pinching, data-resizing or data-zooming. This rule hides the handles from a press on the dial until the pointer lets go, and while the dial glides:
[data-rotating] [data-zriz-item="handle"] {
opacity: 0;
}Each component page lists the attributes that part sets.
Change the colours
Set the colour variables on Root, on one part or on any ancestor. This one makes the scrim white in light mode and darker in dark mode, and the accent blue:
[data-zriz-item="root"] {
--zriz-scrim: light-dark(#ffffffd9, #000000d9);
--zriz-accent: #0a84ff;
}| Variable | Light, dark | What it sets |
|---|---|---|
| --zriz-scrim | #000000c7 in both | The mask outside the crop |
| --zriz-frame | #fff in both | Crop outline and handle marks |
| --zriz-line | #000, #fff | Dial centre line, major ticks, zero dot |
| --zriz-grid | #fff9 in both | Grid lines |
| --zriz-tick | #949494, #666 | Minor ticks on the dial |
| --zriz-accent | #fdca00 in both | Dial centre line while you drag the dial |
Write a value with light-dark() to keep both schemes, or a single colour to use it in both.
The demo above switches between the stylesheet's colours and two light looks of its own. Each look sets all five variables on Root. Drag the dial to see the accent.
Follow dark mode
The dial follows the page's color-scheme: the light values above apply in light mode and the dark values in dark mode. The scrim and the crop frame sit over the photo, so they look the same in both.
With this rule on the page, the parts follow the system setting:
:root {
color-scheme: light dark;
}To pin one part, put data-theme="dark" or the class dark on the part itself. It wins over a themed wrapper. light works on the part itself, but its children stay dark inside a dark wrapper:
<Cropper.Dial data-theme="dark" aria-label="Straighten" />In forced colours mode, the marks the stylesheet paints switch to CanvasText, and the dial's centre line to Highlight during a drag. The browser turns the scrim into a veil of Canvas, so a dark --zriz-scrim shows in the palette's own colour. The other --zriz-* variables keep your values, on Root or on one part, so CSS of your own that reads one needs a forced-colors rule with a system colour.
Style with Tailwind
Without the stylesheet, utilities paint everything, the viewport's clip included. Tailwind's data-* variants read the state attributes:
<Cropper.Root src={file}>
<Cropper.Viewport className="h-80 overflow-clip">
<Cropper.Image />
<Cropper.Mask className="bg-black/60" />
<Cropper.Area
aria-label="Crop area"
className="ring-1 ring-white data-interacting:ring-2 forced-colors:border"
/>
</Cropper.Viewport>
</Cropper.Root>The mask turns 60% black, and the crop gets a white ring that thickens during a gesture. Forced colours mode removes the ring, because it is a box shadow, so forced-colors:border draws the frame there.