The designer is built with a flexible layer system that allows you to create custom layers types, render them on the canvas and add actions to them. This guide will show you how to build and use your own layer types.

## Layer Type

A layer type in designer is an object that defines the properties of a layer and how it is rendered. It accepts the following properties:

| Property | Type | Description |
| --- | --- | --- |
| `type` | `string` | A unique identifier for the layer type. |
| `name` | `string` | The display name shown in the UI. |
| `icon` | `ReactNode` | Optional icon shown in the toolbar. |
| `defaultValues` | `Omit<Layer, "id" \| "type">` | Default values applied when a new layer is created. |
| `render` | `(layer) => ReactNode` | Function that renders the layer on the canvas. |
| `keybinding` | `Keybinding` | Optional keyboard shortcut to add the layer. |

## Organizing Layer Types

Most projects will have more than one layer type, so it's a good idea to organize your layer types into a `layers` folder. Here's an example of how you can organize your layer types:

The following folder structure is a suggestion for scaling as you add more layer types:

```txt
src/
├── layers/
│   ├── index.ts        # Barrel that extends DEFAULT_LAYER_TYPES.
│   ├── shape.tsx       # One file per custom layer type.
│   ├── sticker.tsx
│   ├── chart.tsx
│   └── video.tsx
└── designer.d.ts       # Module augmentation for type safety.
```

Each layer lives in its own file under `src/layers/`. The `index.ts` barrel collects them into a single `layerTypes` array, and `designer.d.ts` augments the `DesignerLayerTypes` interface so the rest of your app gets typed `Layer` values.

Feel free to organize your files however you like — the designer doesn't care where layer types are defined, as long as they're passed to the `Designer` component.

## Creating a Custom Layer

We'll create a new custom layer type called `shape`. It will be a circle shape that can be resized and rotated.

### 1. Define the layer type

Create a new file called `src/layers/shape.tsx` and define the layer type using `createLayerType`. Let's start with only the required properties: `type`, `name`, `defaultValues`, and `render`:

```tsx
import { createLayerType } from "@shadcn/designer";

export const shapeLayer = createLayerType({
  type: "shape",
  name: "Shape",
  defaultValues: {
    name: "Shape",
    value: "shape",
    cssVars: {
      "--width": "100px",
      "--height": "100px",
      "--background-color": "#000000",
    },
  },
  render: (layer) => {
    return (
      <div
        style={{
          ...layer.contentStyle,
        }}
      />
    );
  },
});
```

Here's what each property does:

- **`type`** is the unique identifier the designer uses to discriminate this layer from others.
- **`name`** is the display name shown in the layers panel and elsewhere in the UI.
- **`defaultValues`** is the initial state applied to a new shape layer. The `cssVars` define the layer's size, color, and border radius — these are exposed to `render` via `layer.contentStyle`.
- **`render`** returns the JSX for the layer. Spreading `layer.contentStyle` applies the `cssVars` so the layer reflects user-controlled styling.

### 2. Extend the default layer types

Create a `src/layers/index.ts` barrel that extends the default layer types with your custom layer:

```ts
import { DEFAULT_LAYER_TYPES } from "@shadcn/designer";
import { shapeLayer } from "./shape";

export const layerTypes = [...DEFAULT_LAYER_TYPES, shapeLayer];
```

  Extending the default layer types ensures you can add, remove and override existing layer types easily.

### 3. Augment the module for type safety

Use `InferLayerTypes` to extract type information and augment the `DesignerLayerTypes` interface so that `Layer`, `showForLayerTypes`, and other APIs use your custom types:

```tsx
import type { InferLayerTypes } from "@shadcn/designer";
import type { layerTypes } from "./layers";

declare module "@shadcn/designer" {
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
  interface DesignerLayerTypes extends InferLayerTypes<typeof layerTypes> {}
}
```

The empty interface is required for module augmentation — type aliases can't be merged. The `eslint-disable` comment silences a false positive from the `no-empty-object-type` rule.

### 4. Pass the layer types to the Designer

Pass `layerTypes` to the `Designer` component:

```tsx
import { Designer } from "@shadcn/designer";
import { layerTypes } from "./layers";

function MyDesigner() {
  return (
    <Designer layerTypes={layerTypes}>
      <DesignerContent>
        <DesignerCanvas>
          <DesignerFrame />
        </DesignerCanvas>
      </DesignerContent>
    </Designer>
  );
}
```

The designer is now aware of the shape layer and knows how to render it. But there's no way to actually add one to the canvas yet — let's wire up a keyboard shortcut and an icon.

### 5. Add a keyboard shortcut

Add a `keybinding` to let users add the layer with a keyboard shortcut. The shortcut is automatically registered and shown in the help dialog:

```tsx
export const shapeLayer = createLayerType({
  type: "shape",
  name: "Shape",
  keybinding: {
    key: "s",
    label: "S",
    labelMac: "S",
    description: "Add Shape",
    group: "New Layer",
  },
  // ...
});
```

Press S on your keyboard to add a new shape layer to the canvas. You should see a black square on the canvas.

### 6. Add an icon

Pass an `icon` to make the layer type appear in the `<DesignerToolbar />`. The icon can be any `ReactNode` — an inline SVG, a component from your icon library, or even an emoji:

```tsx
export const shapeLayer = createLayerType({
  type: "shape",
  name: "Shape",
  icon: (
    <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
      <rect width="100%" height="100%" />
    </svg>
  ),
  // ...
});
```

Click the icon in the toolbar to add a new shape layer to the canvas.

## Best Practices

1. **Unique Type**: Always use a unique `type` identifier for your layer.
2. **Default Values**: Provide sensible default values for the layer name, value, and CSS variables.
3. **Keyboard Shortcuts**: Add keyboard shortcuts for better user experience.
4. **Rendering**: Ensure you use the `layer.contentStyle` to style the layer.

Remember to test your layers thoroughly with the designer's transformation tools.

## Custom Layer Action

Now that we have a custom layer, let's add a custom action to the layer. We'll add a custom action to change the color of the shape.

Let's create a `ActionShapeColor` component inside `src/actions/shape-color.tsx` and add the following code.

```tsx
import { Action, ActionControls, ActionLabel } from "@shadcn/designer/ui";

export function ActionShapeColor() {
  return (
    <Action>
      <ActionLabel>Color</ActionLabel>
      <ActionControls>
        // Control here.
      </ActionControls>
    </Action>
  );
}
```

To change the color of the shape, we use the CSS variable `--background-color`. The designer package provides two functions to help us create a custom action to change the color of the shape: `createLayerCssVarAction` and `useLayerCssVarAction`.

```tsx
import {
  Action,
  ActionControls,
  ActionLabel,
  Button,
} from "@shadcn/designer/ui";
import { createLayerCssVarAction, useLayerCssVarAction } from "@shadcn/designer";

const backgroundColor = createLayerCssVarAction("--background-color", "#000000");

export function ActionShapeColor() {
  const [value, setValue] = useLayerCssVarAction(backgroundColor);

  return (
    <Action>
      <ActionLabel>Color {value}</ActionLabel>
      <ActionControls>
        <Button onClick={() => setValue("#ff0000")}>Red</Button>
        <Button onClick={() => setValue("#00ff00")}>Green</Button>
      </ActionControls>
    </Action>
  );
}
```

Import the `ActionShapeColor` component into your `src/App.tsx` file and add the `ActionShapeColor` component to the `<DesignerPane />` with the `showForLayerTypes` prop set to `["shape"]`.

We use the `showForLayerTypes` prop to only show the pane when shape layers are selected.

> If you're following along from the Installation guide, add the code to the `<PanelRight />` component in `src/panel-right.tsx` file.

```tsx
import { Designer, DesignerContent, DesignerCanvas, DesignerFrame, DesignerPanel, DesignerPane, DesignerPaneTitle, DesignerPaneContent } from "@shadcn/designer";

import { ActionShapeColor } from "./actions/shape-color";

export default function App() {
  return (
    <Designer>
      <DesignerContent>
        <DesignerCanvas>
          <DesignerFrame />
        </DesignerCanvas>
        <DesignerPanel>
          <DesignerPane showForLayerTypes={["shape"]}>
            <DesignerPaneTitle>Shape</DesignerPaneTitle>
            <DesignerPaneContent>
              <ActionShapeColor />
            </DesignerPaneContent>
          </DesignerPane>
        </DesignerPanel>
      </DesignerContent>
    </Designer>
  )
}
```

Add a new `Shape` layer to the canvas by clicking on the icon in the toolbar. You should see a new Color action in the right panel. Click the Red or Green button to change the shape's color, and the current value will be reflected in the action label.

If you add more than one shape layer to the canvas, you can drag to select multiple layers and change the color of all of them at once.

## Acting on the Layer Value

`cssVars` work well for styling, but for non-style state — like which shape variant to render — store it on `layer.value` instead. Let's add a Shape Type action that toggles between a square and a circle.

### 1. Update the value to a structured shape

Change the layer's `value` from a string to an object so we can store the variant:

```tsx
export const shapeLayer = createLayerType({
  type: "shape",
  name: "Shape",
  defaultValues: {
    name: "Shape",
    value: { type: "square" },
    cssVars: {
      "--width": "100px",
      "--height": "100px",
      "--background-color": "#000000",
    },
  },
  // ...
});
```

`createLayerType` infers `value` as `{ type: string }` from the default values, so `render` and selectors are typed accordingly.

### 2. Render based on the value

Use `layer.value.type` to return a different SVG for each variant:

```tsx
render: (layer) => {
  return (
    <svg
      viewBox="0 0 100 100"
      preserveAspectRatio="none"
      style={{ ...layer.contentStyle }}
    >
      {layer.value.type === "circle" ? (
        <circle cx="50" cy="50" r="50" />
      ) : (
        <rect width="100" height="100" />
      )}
    </svg>
  );
},
```

### 3. Create the Shape Type action

Create `src/actions/shape-type.tsx`. The action reads the current `value.type` from the selected layer and writes the new one through `useSetLayersValue`:

```tsx
import { useSelectedLayers, useSetLayersValue } from "@shadcn/designer";
import {
  Action,
  ActionControls,
  ActionLabel,
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@shadcn/designer/ui";

export function ActionShapeType() {
  const selectedLayers = useSelectedLayers();
  const setLayersValue = useSetLayersValue();
  const shapeLayers = selectedLayers.filter((layer) => layer.type === "shape");
  const layer = shapeLayers[0];

  // If no shape layer is selected, return null.
  if (!layer) {
    return null;
  }

  return (
    <Action>
      <ActionLabel>Type</ActionLabel>
      <ActionControls>
        <Select
          value={layer.value.type}
          onValueChange={(value) =>
            setLayersValue(shapeLayers, { type: value })
          }
        >
          <SelectTrigger>
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="square">Square</SelectItem>
            <SelectItem value="circle">Circle</SelectItem>
          </SelectContent>
        </Select>
      </ActionControls>
    </Action>
  );
}
```

Filtering to `shapeLayers` narrows `layer.value` to the shape's value type and
also gives `setLayersValue` the right value type for the selected shape layers —
this is the payoff of the module augmentation from earlier.

### 4. Add the action to the pane

Add `<ActionShapeType />` next to `<ActionShapeColor />` in the shape pane:

```tsx
import { ActionShapeColor } from "./actions/shape-color";
import { ActionShapeType } from "./actions/shape-type";

<DesignerPane showForLayerTypes={["shape"]}>
  <DesignerPaneTitle>Shape</DesignerPaneTitle>
  <DesignerPaneContent>
    <ActionShapeType />
    <ActionShapeColor />
  </DesignerPaneContent>
</DesignerPane>
```

Switch the dropdown between Square and Circle — the canvas re-renders the matching SVG using the same `cssVars` for size and color.

## Next Steps

- See the [Custom Layers](/docs/examples/layer-custom) section for more examples of custom layers with complex actions and controls.
- Learn how to [use the theme system](/docs/concepts/theming) to customize the designer's appearance.
- Learn how to [use the unit system](/docs/concepts/unit-system).
- Learn more about the [designer components](/docs/reference/designer) and [hooks](/docs/reference/hooks).