# Styling

> [llms.txt](https://zriz.dev/llms.txt)

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](https://zriz.dev/docs/bundle-size.md) lists its weight:

```ts
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](https://zriz.dev/docs/viewport.md) 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](https://zriz.dev/docs/vanilla-js.md).

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:

```css
@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:

```tsx
<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](https://zriz.dev/docs/composition.md).

## 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](https://zriz.dev/docs/dial.md) 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:

```css
[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:

```css
[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 page at https\://zriz.dev/docs/styling runs this cropper: a 3:2 photo, the straighten dial with its value, a track of default, ink and soft colours, and open, reset and download in the corners.

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:

```css
: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:

```tsx
<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:

```tsx
<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.
