)
}
```
See the [`useSetLayersValue`](/docs/reference/hooks#usesetlayersvalue) and [`useSetLayersMeta`](/docs/reference/hooks#usesetlayersmeta) references for target forms and generic usage.
### Shared Border, Corner, and Padding Controls
The border, corner, and padding actions have been rebuilt around shared control behavior. They now handle mixed values across multi-selection, expose grouped and per-side controls, and share the same input model used by other numeric designer actions.
- `ActionCorner` supports grouped radius editing and independent corner values.
- `ActionPadding` supports grouped padding and per-side padding.
- `ActionBorder` supports border width and color controls with shared multi-selection behavior.
- `useSharedValues` is available for custom controls that need the same "all selected values match" behavior.
See the [upgrade guide](/docs/guides/update) for upgrading your project to v2.0.0.
---
## Restructured Docs, llms.txt, and Markdown
We've reorganized the docs and added support for LLMs and markdown.
### Docs
The docs are now split into four sections: **Concepts**, **Guides**, **Examples**, and **Reference**. Old URLs redirect automatically.
### llms.txt
We've added [`/llms.txt`](/llms.txt) and [`/llms-full.txt`](/llms-full.txt) endpoints for LLMs and AI tools. Every docs page is also available as markdown by appending `.md` to the URL. You'll find a "View as Markdown" link in the sidebar on each page.
## v1.1.0
We just tagged `@shadcn/designer` v1.1.0 with a new unit system and unified Designer Tool API for managing canvas tools.
- [Unit System](#unit-system)
- [Designer Tool](#designer-tool)
- [Input Scrubber](#input-scrubber)
### Unit System
We now let you work in pixels, millimeters, inches, centimeters, or points with DPI-aware conversions.
- Introduced hooks for reacting to unit changes: `useUnitSystem`, `useSetUnitSystem`, `useDPI`, and `useSetDPI`.
- Added `ActionUnitSelector` for swapping units directly from the toolbar.
- Added a shared `units` library with helpers like `toPixels`, `fromPixels`, `parseValueWithUnit`, and `formatValue` plus `DEFAULT_DPI` and `DPI_PRESETS` presets.
- Updated editor defaults to store `unitSystem` and `dpi`, convert paper sizes from millimeters, and clamp dimension inputs at `0`.
- Refined dimension displays to show converted values with trimmed decimals and localized unit abbreviations.
```tsx
import { useUnitSystem, useSetUnitSystem } from "@shadcn/designer"
function UnitSwitcher() {
const unitSystem = useUnitSystem()
const setUnitSystem = useSetUnitSystem()
return (
Units: {unitSystem}
)
}
```
### Example
This example demonstrates how to use the unit system to display and work with measurements in different units.
### Designer Tool
We've introduced a new tool selection system that allows you to switch between different interaction modes on the canvas. This release includes two core tools:
- **Move Tool (V)** - The default tool for selecting, moving, resizing, and rotating layers
- **Hand Tool (H)** - Pan around the canvas without selecting or moving layers
#### Hooks
**`useDesignerTool()`** - Get the currently active tool
```tsx
import { useDesignerTool } from "@shadcn/designer"
function ToolIndicator() {
const tool = useDesignerTool()
return (
)
}
```
**`useSetDesignerTool()`** - Change the active tool programmatically
```tsx
import { useSetDesignerTool } from "@shadcn/designer"
function ToolSwitcher() {
const setTool = useSetDesignerTool()
return (
)
}
```
#### ActionToolbarTool
Dropdown toolbar button for switching between tools
```tsx
import { ActionToolbarTool } from "@shadcn/designer"
```
See the [Hooks Reference](/docs/reference/hooks#usedesignertool) for complete API documentation.
#### Example
Use the Cursor and Hand buttons in the toolbar to switch between the move and hand tools.
### Input Scrubber
The [`InputGroup`](/docs/reference/ui#inputgroup) component now supports scrubbing for faster adjustments without losing precision.
- Hold the mouse button and drag to scrub an input value.
- Added a `useScrubber` hook that handles pointer locking, acceleration, and modifier shortcuts for fine or coarse adjustments.
- Exposed the hook through the designer entry point so custom controls can share the same interaction model as built-in actions.
- Bundled a visual scrubber cursor that renders while dragging to make state changes easier to track.
```tsx
import * as React from "react"
import { useScrubber } from "@shadcn/designer"
function DimensionScrubber() {
const { scrubProps, value } = useScrubber({
value: 120,
onChange: (next) => console.log("Width", next),
})
return (
)
}
```
#### Continuous Button Press
We've added hold-to-repeat controls to make numeric inputs feel more responsive.
- Added a continuous press controller that emits repeated `onChange` events while increment or decrement buttons remain pressed.
- Integrated the behavior into `InputNumber`, matching keyboard repeat cadence and respecting min/max constraints.
- Tuned acceleration so long presses ramp quickly without overshooting small adjustments.
## v1.0.0
We're excited to announce the release of `@shadcn/designer` v1.0.0 (stable). If you do not have a license, you can get one [here](/docs/pricing).

Here's a list of the new features and improvements:
- **mode** - new `mode` prop to the `` component to switch between different layer modes. See the [docs](/docs/reference/designer#mode) for more information.
- **onMount** - We've added a new `onMount` prop to the `` component to run a callback when the designer is mounted. Useful for doing additional setup when the designer is ready.
- **defaultLayers** - the `layers` prop has been renamed to `defaultLayers` for uncontrolled components.
- **layers** and **onLayersChange** - We've added new props for controlled `` components.
- We've also introduced a layer signature optimization to reduce the number of re-renders.
- We've improved layer snapping to be more accurate.
See the [roadmap](/docs/roadmap) for what's coming next.
**Thank you to all the early adopters who have been using the designer and providing feedback: Mikkel, Sebastian, Piotr, Santosh, Jenny, Alex, Maria, David K., David E., and Sarah.**
---
## Installation
Learn how to install the @shadcn/designer package and get started with the designer.
## License Required
You will need a license key to install the package. You can learn more about the pricing and purchase a Yearly Business License [here](/docs/pricing).
## Create Project
First, let's setup a new Vite + Tailwind project.
> **Note:** You can use any React framework with the designer. This guide uses
> Vite + React + Tailwind. The only requirement is that you have Tailwind
> installed.
```bash
npx create-vite@latest my-designer --template react-ts
```
## Configure Tailwind
The `@shadcn/designer` package is built with Tailwind CSS. You will need to install Tailwind and configure it in your project.
### Install Tailwind
Add the following dependencies to your project.
```bash
npm install tailwindcss @tailwindcss/vite
```
### Configure the Vite plugin
Import the Tailwind Vite plugin in your `vite.config.ts` file.
```tsx
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [react(), tailwindcss()],
});
```
### Import Tailwind CSS
Replace the content of your `src/index.css` file with the following.
```tsx
@import "tailwindcss";
```
## Install Package
Next, let's install the package. You will need a license key to install the package. You can purchase a Yearly Business License [here](/docs/pricing).
```bash
npm install https://ds.shadcn.com/registry\?license\=YOUR_LICENSE_KEY
```
For a standalone setup, import the `Designer` component from `@shadcn/designer` and the full stylesheet from `@shadcn/designer/styles.css`.
```tsx
import { Designer } from "@shadcn/designer";
import "@shadcn/designer/styles.css";
export default function App() {
return ;
}
```
Make sure you import `@shadcn/designer/styles.css` once in standalone apps. This should be imported after your own styles.
> **Existing Tailwind app:** If your app already has its own Tailwind CSS 4
> theme, base reset, and utilities, do not import
> `@shadcn/designer/styles.css`. Import `@shadcn/designer/core.css` from your
> global CSS file and add `@source "../node_modules/@shadcn/designer";` so your
> app generates the Designer utility classes.
```css
@import "tailwindcss";
@import "@shadcn/designer/core.css";
@source "../node_modules/@shadcn/designer";
```
The examples below omit the stylesheet import. Configure one of the stylesheet options above once in your app.
> **Note:** The Designer will take up the height of its container. The `h-svh` class is used to make the designer take up the full height of the viewport.
## Components
The `Designer` is built to be composable. You build your custom designer by composing the components that you need.
The following will setup a basic designer with a canvas and a frame. The default frame size is `1024x1024`.
```tsx
import {
Designer,
DesignerContent,
DesignerCanvas,
DesignerFrame,
} from "@shadcn/designer";
export default function App() {
return (
);
}
```
## Layers
Now that your designer is setup, you can add layers to it. Press T on your keyboard to add a new `text` layer.
You will see a new text layer added to the canvas. You can drag the text layer around the canvas and use the handles to resize it.
The designer comes with the following layer types by default: `frame`, `text` and `image`.
You can extend the designer by adding your own layer types. See the [Custom Layer](/docs/concepts/layers) example for more information.
## Toolbar
Let's place a toolbar with buttons to add layers to the canvas.
```tsx
import {
Designer,
DesignerContent,
DesignerCanvas,
DesignerFrame,
DesignerToolbar,
DesignerToolbarGroup,
ActionToolbarAddLayer,
} from "@shadcn/designer";
export default function App() {
return (
);
}
```
You should see a new toolbar at the bottom of the canvas. The toolbar has one action to add a new layer.
## Panels
Now that we have a canvas and we can add layers to it, let's add a `` to display layer controls.
```tsx
import {
Designer,
DesignerContent,
DesignerCanvas,
DesignerFrame,
DesignerToolbar,
DesignerToolbarGroup,
ActionToolbarAddLayer,
DesignerPanel,
} from "@shadcn/designer";
export default function App() {
return (
);
}
```
This will place a panel on the right side of the canvas. We can have multiple panels in the designer. Let's add a panel to the left side of the canvas.
```tsx
import {
Designer,
DesignerContent,
DesignerCanvas,
DesignerFrame,
DesignerToolbar,
DesignerToolbarGroup,
ActionToolbarAddLayer,
DesignerPanel,
} from "@shadcn/designer";
export default function App() {
return (
);
}
```
## Panes
We use `` to display and group controls. Let's add a new pane to the right panel.
```tsx
import {
Designer,
DesignerContent,
DesignerCanvas,
DesignerFrame,
DesignerToolbar,
DesignerToolbarGroup,
ActionToolbarAddLayer,
DesignerPanel,
DesignerPane,
DesignerPaneTitle,
DesignerPaneContent,
} from "@shadcn/designer";
export default function App() {
return (
LayersHello World
);
}
```
You should see a new pane with the title "Layers" and the content "Hello World" in the right panel.
## Actions and Controls
To display and control the properties of a layer, we use [``](/docs/reference/actions) components.
The `@shadcn/designer` package ships with several actions out of the box. **We provide the necessary API and components to create your own custom actions.**
Let's add a [``](/docs/reference/actions#actionposition) to the right panel to display the position of the layer.
```tsx
import {
Designer,
DesignerContent,
DesignerCanvas,
DesignerFrame,
DesignerToolbar,
DesignerToolbarGroup,
ActionPosition,
ActionToolbarAddLayer,
DesignerPanel,
DesignerPane,
DesignerPaneTitle,
DesignerPaneContent,
} from "@shadcn/designer";
export default function App() {
return (
Layers
);
}
```
You should see a new _Position_ action in the right panel. It comes with `X` and `Y` inputs to control the position of the layer. If you click and drag a layer you should see the `X` and `Y` values change.
## More Actions
Let's build a custom panel with more actions and import it into our designer.
Create a new file called `src/panel-right.tsx` and add the following code.
```tsx
import {
ActionFill,
ActionPadding,
ActionBoxShadow,
DesignerPanel,
DesignerPane,
DesignerPaneTitle,
DesignerPaneContent,
ActionPosition,
ActionSize,
ActionCorner,
ActionBorder,
ActionRotate,
ActionDirection,
ActionTextValue,
ActionImageFit,
ActionImageFilter,
ActionFont,
ActionColor,
ActionFontSize,
ActionLineHeight,
ActionLetterSpacing,
ActionTextAlign,
ActionAlignItems,
ActionFontStyle,
ActionTextDecoration,
ActionTextTransform,
ActionTextShadow,
ActionTextStroke,
} from "@shadcn/designer";
export function PanelRight() {
return (
LayerStylesTransformsImageFiltersText
);
}
```
Then import the `PanelRight` component into your `src/App.tsx` file.
```tsx
import {
Designer,
DesignerContent,
DesignerCanvas,
DesignerFrame,
DesignerToolbar,
DesignerToolbarGroup,
ActionToolbarAddLayer,
DesignerPanel,
} from "@shadcn/designer";
import { PanelRight } from "./panel-right";
export default function App() {
return (
);
}
```
You should see a new panel on the right side of the canvas. The panel has the actions to control the layer.
See the [Actions](/docs/reference/actions) section for more information on available actions.
## Next Steps
- [Add a custom layer to the designer](/docs/concepts/layers)
- [Browse more examples](/docs/examples)
- Read more about [components](/docs/reference/designer) and [hooks](/docs/reference/hooks)
---
## Update Guide
How to update shadcn/designer to the latest version.
## Upgrade to 2.0
Version 2.0 adds typed layer utilities, group layers, and new setters for
layer values and meta.
Follow the steps below to update to 2.0.0.
### Update package.json to 2.0.0
In your `package.json` file, update the registry URL to `version=2.0.0`:
```json
{
"name": "my-designer",
"dependencies": {
"@shadcn/designer": "https://ds.shadcn.com/registry?version=2.0.0&license=YOUR_LICENSE_KEY"
}
}
```
Then run `pnpm install` to pull version 2.0.0 from the registry.
### Use createLayerType
Custom layer definitions should now use `createLayerType`.
```tsx
import { createLayerType } from "@shadcn/designer"
export const shapeLayer = createLayerType({
type: "shape",
name: "Shape",
defaultValues: {
name: "Shape",
value: { type: "square" },
cssVars: {
"--width": "100px",
"--height": "100px",
"--background-color": "#000000",
},
},
render: (layer) => {
return
},
})
```
Keep your layer types in an inferred array. Do not annotate the array as
`LayerType[]`.
```ts
import { DEFAULT_LAYER_TYPES } from "@shadcn/designer"
import { shapeLayer } from "./shape"
export const layerTypes = [...DEFAULT_LAYER_TYPES, shapeLayer]
```
Register the array for type-safe `Layer`, `showForLayerTypes`, and layer setter
usage:
```ts
import type { InferLayerTypes } from "@shadcn/designer"
import type { layerTypes } from "./layers"
declare module "@shadcn/designer" {
interface DesignerLayerTypes extends InferLayerTypes {}
}
```
### Review group layers
`DEFAULT_LAYER_TYPES` now includes a built-in `group` layer type. Groups use
`parentId` to store nested layers:
```ts
type Layer = {
id: string
type: string
parentId?: string
}
```
2.0 adds:
- `GROUP_LAYERS` and `UNGROUP_LAYERS` actions.
- Ctrl/Cmd + G to group selected layers.
- Ctrl/Cmd + Shift + G to ungroup.
- Nested layer rendering and drag-and-drop parenting in ``.
- Layout controls for display, direction, alignment, wrapping, and gap.
New actions are available for working with groups:
```tsx
import { ActionLayoutWrap, ActionLayoutAlign, ActionLayoutDirection, ActionLayoutDisplay, ActionLayoutGap, ActionLayoutJustify, ActionLayoutPadding } from "@shadcn/designer"
```
### Replace value and meta writes
Use the new `useSetLayersValue` and `useSetLayersMeta` hooks to set layer values and meta. Keep `useSetLayersProperty` for shared fields such as `name` and `isLocked`.
```tsx
import { useSetLayersProperty } from "@shadcn/designer"
const setLayersProperty = useSetLayersProperty()
setLayersProperty(selectedLayerIds, "value", { type: "circle" })
```
```tsx
import { useSetLayersValue } from "@shadcn/designer"
const setLayersValue = useSetLayersValue()
setLayersValue<"shape">(selectedLayerIds, { type: "circle" })
```
Use `useSetLayersMeta` for `layer.meta`:
```tsx
import { useSetLayersProperty } from "@shadcn/designer"
const setLayersProperty = useSetLayersProperty()
setLayersProperty(selectedLayerIds, "meta", { approved: true })
```
```tsx
import { useSetLayersMeta } from "@shadcn/designer"
const setLayersMeta = useSetLayersMeta()
setLayersMeta<"card">(selectedLayerIds, { approved: true })
```
When you only have IDs, pass the layer type as a generic:
```tsx
setLayersValue<"shape">(selectedLayerIds, { type: "square" })
setLayersMeta<"card">(selectedLayerIds, { approved: true })
```
### 2.0 checklist
- Update the registry URL version and run `pnpm install`.
- Convert custom layer definitions to `createLayerType`.
- Register custom layer types with `InferLayerTypes`.
- Preserve and handle `parentId` in custom layer-list logic.
- Replace `value` writes with `useSetLayersValue`.
- Replace `meta` writes with `useSetLayersMeta`.
---
## Update to the latest minor version
In your `package.json` file, update the `version={PACKAGE_VERSION}` to the latest version. The latest version is **{PACKAGE_VERSION}**.
```json
{
"name": "my-designer",
"dependencies": {
"@shadcn/designer": "https://ds.shadcn.com/registry?version=PACKAGE_VERSION&license=YOUR_LICENSE_KEY"
}
}
```
Then run `pnpm install` to pull the latest version from the registry.
---
## Designer
Learn how to use the designer component.
The `Designer` component is the heart of the `@shadcn/designer` package. It gives you the canvas, frame, tooling, and data model you need to build custom editors, from a single framed artboard to a multi-pane design suite.
## Quick start
Use `Designer`, `DesignerContent`, `DesignerCanvas`, and `DesignerFrame` together to render the default editor experience. This mirrors the setup used in the basic example.
```tsx
import * as React from "react"
import {
Designer,
DesignerCanvas,
DesignerContent,
DesignerFrame,
} from "@shadcn/designer"
export function CustomDesigner() {
return (
)
}
```
> **Tip:** `Designer` is layout agnostic. Wrap it inside your own flex or grid containers to create headers, panels, and footers around the canvas.
The `Designer` component currently targets a single frame workflow. Support for multiple frames is in development and will land soon.
## Layout primitives
- `DesignerContent` renders the scrollable workspace that holds the canvas.
- `DesignerCanvas` provides pan, zoom, and selection handling for all layers.
- `DesignerFrame` draws the editable artboard and renders the current layer tree.
- `DesignerStaticFrame` renders a fixed, read-only preview from layer data.
- `DesignerToolbar`, `DesignerPanel`, and `DesignerPane` help you compose custom UI around the canvas.
Combine these building blocks with your own UI from `@shadcn/ui` to design the editor shell that fits your product.
## Core props
| Prop | Type | Description |
| --- | --- | --- |
| `defaultLayers` | `Layer[]` | Pre-populated layers for uncontrolled mode. Useful for seeding from persisted data. |
| `layers` | `Layer[]` | Enables controlled mode. Mirror state updates with `onLayersChange`. |
| `onLayersChange` | `(layers: Layer[]) => void` | Called whenever the editor mutates layers. Required when `layers` is provided. |
| `layerTypes` | `readonly LayerType[]` | Extends or replaces the default layer registry. Tie custom renderers and tooling to your data. |
| `frameSize` | `{ width: number; height: number; unit?: Unit }` | Sets the initial dimensions and unit for `DesignerFrame`. Combine with unit actions for print workflows. |
| `unitSystem` | `Unit` | Overrides the global unit (px, in, mm, etc.). Drives rulers, snapping, and measurement UI. |
| `dpi` | `number` | Define dots per inch for pixel-to-unit conversions. |
| `mode` | `"single" | "multiple"` | Toggle between editing one layer at a time or multi-select workflows. |
| `keybindings` | `Record` | Customize shortcuts or provide localized variations. |
| `debug` | `boolean` | Surface debug visuals while developing integrations. |
| `onMount` | `() => void` | Run side effects (analytics, focus) after the editor mounts. |
## Uncontrolled mode
`Designer` manages its own layer state when you pass `defaultLayers`. This is ideal when you only need to load an initial document.
```tsx
import * as React from "react"
import {
Designer,
DesignerCanvas,
DesignerContent,
DesignerFrame,
type Layer,
} from "@shadcn/designer"
const DEFAULT_LAYERS: Layer[] = [
{
id: "layer-1",
name: "Hero",
type: "text",
value: "Hero",
cssVars: {
"--width": "300px",
"--height": "100px",
"--content-font-size": "64px",
"--content-font-weight": "700",
"--content-color": "#000000",
"--translate-x": "80px",
"--translate-y": "56px",
},
},
]
export function DesignerWithDefaults() {
return (
)
}
```
## Controlled mode
When you need to sync layers with your own state or a backend, switch to controlled mode by providing `layers` and `onLayersChange`.
```tsx
import * as React from "react"
import {
Designer,
DesignerCanvas,
DesignerContent,
DesignerFrame,
type Layer,
} from "@shadcn/designer"
export function DesignerControlled() {
const [layers, setLayers] = React.useState([])
return (
)
}
```
## Custom layer types
Augment the editor by extending `DEFAULT_LAYER_TYPES` with your own definitions. Use `createLayerType` so the layer value is inferred in `render`, then pass the final array to `Designer`.
```tsx
import * as React from "react"
import {
DEFAULT_LAYER_TYPES,
Designer,
DesignerContent,
DesignerCanvas,
DesignerFrame,
createLayerType,
} from "@shadcn/designer"
import { IconPlayerPlay } from "@tabler/icons-react"
const videoLayer = createLayerType({
type: "video",
name: "Video",
icon: ,
defaultValues: {
name: "Video",
value: {
src: "https://cdn.example.com/video.mp4",
},
cssVars: {
"--width": "320px",
"--height": "180px",
"--background-color": "#000000",
},
},
keybinding: {
key: "v",
label: "V",
labelMac: "V",
description: "Add video layer",
group: "New Layer",
},
render: (layer) => {
return (
)
},
})
const layerTypes = [...DEFAULT_LAYER_TYPES, videoLayer]
export function DesignerWithVideoLayer() {
return (
)
}
```
For app-wide type safety, export the same `layerTypes` array from your layers folder and augment `DesignerLayerTypes` with `InferLayerTypes`, as shown in [Layers](/docs/concepts/layers).
## Composing the UI
Add panels, toolbars, and inspector panes around the canvas to create a full editor experience.
```tsx
import * as React from "react"
import {
Designer,
DesignerCanvas,
DesignerContent,
DesignerFrame,
DesignerHeader,
DesignerPanel,
DesignerToolbar,
DesignerToolbarGroup,
DesignerToolbarButton,
} from "@shadcn/designer"
import { Button } from "@shadcn/ui/button"
export function DesignerWithShell() {
return (
Brand Poster
AddDuplicate
Layer
Select a layer to edit properties.
)
}
```
## Next steps
- Walk through [Layers](../layers) to learn how layer data, transforms, and inspectors work together.
- Dive into [Unit System](../unit-system) for precision control over print and physical products.
- Explore the `/examples` directory for more complete editor shells, including print and marketing templates.
---
## Layers
Learn how to work with layers and layer types in designer.
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` | 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 (
);
},
});
```
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 {}
}
```
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 (
);
}
```
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 ``. 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: (
),
// ...
});
```
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 (
Color
// Control here.
);
}
```
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 (
Color {value}
);
}
```
Import the `ActionShapeColor` component into your `src/App.tsx` file and add the `ActionShapeColor` component to the `` 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 `` 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 (
Shape
)
}
```
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 (
);
},
```
### 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 (
Type
);
}
```
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 `` next to `` in the shape pane:
```tsx
import { ActionShapeColor } from "./actions/shape-color";
import { ActionShapeType } from "./actions/shape-type";
Shape
```
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).
---
## Theming
Add custom themes to your designer.
Designer uses the [shadcn/ui](https://ui.shadcn.com/docs/theming) theming system. How you theme it depends on the stylesheet entry point you use.
## Standalone apps
If you import the full stylesheet, theme Designer with `--ds-*` variables.
```tsx
import "@shadcn/designer/styles.css";
```
Add the following CSS variables to your `index.css` file. Update the values to match your theme.
```css
@import "tailwindcss";
:root {
--ds-radius: 0.625rem;
--ds-background: oklch(1 0 0);
--ds-foreground: oklch(0.145 0 0);
--ds-card: oklch(1 0 0);
--ds-card-foreground: oklch(0.145 0 0);
--ds-popover: oklch(1 0 0);
--ds-popover-foreground: oklch(0.145 0 0);
--ds-primary: oklch(0.205 0 0);
--ds-primary-foreground: oklch(0.985 0 0);
--ds-muted: oklch(0.97 0 0);
--ds-muted-foreground: oklch(0.556 0 0);
--ds-accent: oklch(0.97 0 0);
--ds-accent-foreground: oklch(0.205 0 0);
--ds-destructive: oklch(0.577 0.245 27.325);
--ds-border: oklch(0.922 0 0);
--ds-input: oklch(0.97 0 0);
--ds-ring: oklch(0.708 0 0);
}
.dark {
--ds-background: oklch(0.145 0 0);
--ds-foreground: oklch(0.985 0 0);
--ds-card: oklch(0.205 0 0);
--ds-card-foreground: oklch(0.985 0 0);
--ds-popover: oklch(0.269 0 0);
--ds-popover-foreground: oklch(0.985 0 0);
--ds-primary: oklch(0.922 0 0);
--ds-primary-foreground: oklch(0.205 0 0);
--ds-muted: oklch(0.269 0 0);
--ds-muted-foreground: oklch(0.708 0 0);
--ds-accent: oklch(0.371 0 0);
--ds-accent-foreground: oklch(0.985 0 0);
--ds-destructive: oklch(0.704 0.191 22.216);
--ds-border: oklch(1 0 0 / 10%);
--ds-input: oklch(1 0 0 / 15%);
--ds-ring: oklch(0.556 0 0);
}
```
## Existing Tailwind apps
If your app already has Tailwind CSS 4, use the core stylesheet and let your app generate Designer's utility classes.
```css
@import "tailwindcss";
@import "@shadcn/designer/core.css";
@source "../node_modules/@shadcn/designer";
```
In this setup, Designer uses your app's semantic Tailwind variables. Configure those variables in your app theme.
```css
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
}
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.97 0 0);
--ring: oklch(0.708 0 0);
}
```
## Scoped Designer theme
To give Designer a different theme from the rest of your app while using `core.css`, scope variable overrides to a wrapper.
```tsx
import { Designer } from "@shadcn/designer";
export function MyDesigner() {
return (
{/* ... */}
);
}
```
```css
.designer-theme {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.97 0 0);
--ring: oklch(0.708 0 0);
}
.dark .designer-theme {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.269 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.371 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
}
```
With `core.css`, scope the variables that your Tailwind theme maps to semantic utilities. Do not scope generated utility classes such as `.bg-background` or `.border-input`.
---
## Unit System
Work with multiple measurement units including pixels, millimeters, inches, centimeters, and points for both print and digital design workflows.
The Designer package includes a comprehensive unit system that supports multiple measurement scales beyond pixels. This allows you to work in your preferred units, whether designing for print (mm, inches) or digital (pixels, points), while maintaining precision and consistency throughout the design process.
## Supported Units
| Unit | Symbol | Description | Common Use Case |
|------|--------|-------------|-----------------|
| Pixels | `px` | Screen pixels | Web design, UI |
| Millimeters | `mm` | Metric measurement | Print design, Europe |
| Inches | `in` | Imperial measurement | Print design, US |
| Centimeters | `cm` | Metric measurement | Large print formats |
| Points | `pt` | Typographic points (1/72 inch) | Typography, print |
## Key Features
- **Native Unit Storage**: Values are stored with their original units (e.g., "100mm") ensuring exports maintain correct measurements.
- **DPI-Aware Conversion**: Supports different DPI settings for screen (96 DPI) and print (300 DPI) contexts.
- **Backwards Compatible**: Existing pixel-based designs continue to work without modification.
- **Real-time Conversion**: Switch between units on the fly with automatic conversion.
- **Precision**: All converted values use appropriate decimal places for clean display.
## Basic Setup
Configure the Designer component with your preferred units:
```tsx
import { Designer } from "@shadcn/designer"
function PrintDesigner() {
return (
{/* Designer content */}
)
}
```
## Frame Size with Units
The `frameSize` prop now accepts an optional `unit` field:
```tsx
// A4 paper in millimeters
frameSize={{ width: 210, height: 297, unit: "mm" }}
// US Letter in inches
frameSize={{ width: 8.5, height: 11, unit: "in" }}
// Screen size in pixels (default)
frameSize={{ width: 1920, height: 1080 }} // unit defaults to "px"
```
## Hooks
### useUnitSystem
Returns the current unit system being used for display.
```tsx
import { useUnitSystem } from "@shadcn/designer"
function MyComponent() {
const unitSystem = useUnitSystem() // "px" | "mm" | "in" | "cm" | "pt"
return
Current unit: {unitSystem}
}
```
### useSetUnitSystem
Sets the unit system for displaying values.
```tsx
import { useSetUnitSystem } from "@shadcn/designer"
function UnitSwitcher() {
const setUnitSystem = useSetUnitSystem()
return (
)
}
```
### useDPI
Returns the current DPI setting.
```tsx
import { useDPI } from "@shadcn/designer"
function DPIDisplay() {
const dpi = useDPI() // Default: 96
return
Current DPI: {dpi}
}
```
### useSetDPI
Sets the DPI for unit conversions.
```tsx
import { useSetDPI } from "@shadcn/designer"
function DPISwitcher() {
const setDPI = useSetDPI()
return (
)
}
```
## Utility Functions
All unit utility functions are available from `@shadcn/designer/utils`:
### toPixels
Converts any unit value to pixels.
```tsx
import { toPixels } from "@shadcn/designer/utils"
// Convert 25.4mm to pixels at 96 DPI
const dpi = 96
const pixels = toPixels(25.4, "mm", dpi) // 96 pixels (1 inch)
// Convert 72 points to pixels at 96 DPI
const pixels2 = toPixels(72, "pt", dpi) // 96 pixels
```
### fromPixels
Converts pixels to a target unit.
```tsx
import { fromPixels } from "@shadcn/designer/utils"
// Convert 96 pixels to millimeters at 96 DPI
const dpi = 96
const mm = fromPixels(96, "mm", dpi) // 25.4mm
// Convert 300 pixels to inches at 300 DPI
const printDpi = 300
const inches = fromPixels(300, "in", printDpi) // 1 inch
```
### convertUnit
Converts a value from one unit to another.
```tsx
import { convertUnit } from "@shadcn/designer/utils"
// Convert 100mm to inches at 300 DPI
const printDpi = 300
const inches = convertUnit(100, "mm", "in", printDpi) // ~3.94 inches
// Convert 2 inches to pixels at 96 DPI
const screenDpi = 96
const pixels = convertUnit(2, "in", "px", screenDpi) // 192 pixels
```
### parseValueWithUnit
Parses a string value with unit into its components.
```tsx
import { parseValueWithUnit } from "@shadcn/designer/utils"
const parsed = parseValueWithUnit("100mm")
// Returns: { value: 100, unit: "mm" }
const parsed2 = parseValueWithUnit("2.5in")
// Returns: { value: 2.5, unit: "in" }
// Numbers without units default to pixels
const parsed3 = parseValueWithUnit("200")
// Returns: { value: 200, unit: "px" }
```
### Additional Utilities
```tsx
import {
valueToPixels, // Convert "100mm" string to pixels
pixelsToValue, // Convert pixels to "100mm" string
formatValue, // Format with appropriate precision
getUnitDisplayName, // Get full name ("Millimeters")
getUnitShortName, // Get abbreviation ("mm")
roundForUnit, // Round to unit's precision
DEFAULT_DPI, // 96 (CSS standard)
DPI_PRESETS // { screen: 96, print: 300, retina: 192 }
} from "@shadcn/designer/utils"
```
## Unit Selector Component
Add a unit selector to your toolbar for easy switching:
```tsx
import {
ActionUnitSelector,
DesignerToolbar,
DesignerToolbarGroup
} from "@shadcn/designer"
function MyToolbar() {
return (
)
}
```
## Layer Storage
Layers store values with their original units, preserving precision:
```tsx
const layer: Layer = {
id: "1",
name: "Business Card",
type: "frame",
value: "",
cssVars: {
// Dimensions in millimeters
"--width": "85mm",
"--height": "55mm",
"--translate-x": "10mm",
"--translate-y": "10mm",
// Border in points
"--border-width": "1pt",
// Font size in points
"--content-font-size": "12pt",
// Mixed units are supported
"--padding-top": "5mm",
"--margin-left": "0.25in",
}
}
```
### Automatic Conversion
When switching units, values are converted for display but stored in their original units:
1. Layer stores: `"--width": "100mm"`
2. User switches to inches
3. Display shows: 3.94"
4. Internally still stored as: "100mm"
5. Export preserves: "100mm"
## Understanding DPI
DPI (Dots Per Inch) affects how physical units convert to pixels:
- **96 DPI** (Screen): 1 inch = 96 pixels (CSS standard)
- **300 DPI** (Print): 1 inch = 300 pixels (print quality)
### Conversion Examples
```tsx
// At 96 DPI (screen)
const screenDpi = 96
toPixels(1, "in", screenDpi) // 96px
toPixels(25.4, "mm", screenDpi) // 96px (25.4mm = 1 inch)
// At 300 DPI (print)
const printDpi = 300
toPixels(1, "in", printDpi) // 300px
toPixels(25.4, "mm", printDpi) // 300px
```
## Precision and Rounding
All unit conversions use appropriate precision for clean display:
- **Pixels**: Always rounded to integers
- **Points**: 1 decimal place
- **Millimeters/Centimeters/Inches**: 2 decimal places
This ensures values like "100.00mm" display as "100mm" while maintaining precision where needed.
## Dynamic Unit Switching
```tsx
"use client"
import { useState } from "react"
import { Designer } from "@shadcn/designer"
import type { Unit } from "@shadcn/designer/utils"
function DesignerWithUnitToggle() {
const [unitSystem, setUnitSystem] = useState("px")
const [dpi, setDPI] = useState(96)
return (
{/* Designer content */}
)
}
```
## Migration Guide
### For Existing Projects
Existing projects using pixels will continue to work without any changes:
1. **No Breaking Changes**: All existing pixel-based layers work as before
2. **Optional Upgrade**: Add unit support when needed
3. **Gradual Migration**: Mix pixel and unit-based layers
### Adding Unit Support
To add unit support to an existing project:
```tsx
// Before (pixels only)
{/* content */}
// After (with unit support)
{/* Add unit switcher */}
{/* content */}
```
### Converting Existing Layers
To convert existing pixel values to units:
```tsx
import { pixelsToValue } from "@shadcn/designer/utils"
// Convert existing pixel layer to use millimeters
const dpi = 96
const convertedLayer = {
...existingLayer,
cssVars: {
...existingLayer.cssVars,
// Convert 400px to mm at 96 DPI
"--width": pixelsToValue(400, "mm", dpi), // "105.83mm"
"--height": pixelsToValue(300, "mm", dpi), // "79.38mm"
}
}
```
## Best Practices
1. **Choose Appropriate Units**: Use mm/inches for print, pixels for web
2. **Set Correct DPI**: 96 for screen, 300+ for print
3. **Store Native Units**: Let layers store their original units
4. **Consistent Units**: Use the same unit type within a design
5. **Test Exports**: Verify exported values maintain correct units
## Troubleshooting
### Common Issues
**Q: Why do my values change when switching units?**
A: Values are converted for display but stored in original units. The visual change is expected.
**Q: How do I ensure accurate print dimensions?**
A: Use the appropriate DPI (300+) and native print units (mm/inches).
**Q: Can I mix different units in one design?**
A: Yes, each layer property can use different units as needed.
**Q: What happens to units when exporting?**
A: Exported values maintain their original units without conversion.
## API Reference
### Types
```tsx
type Unit = "px" | "mm" | "in" | "cm" | "pt"
interface ValueWithUnit {
value: number
unit: Unit
}
interface FrameSize {
width: number
height: number
unit?: Unit // Optional, defaults to "px"
}
```
### Constants
```tsx
const DEFAULT_DPI = 96
const DPI_PRESETS = {
screen: 96,
print: 300,
retina: 192,
}
```
## Supported Dimension Properties
The following CSS variable properties automatically convert between units:
- Position: `--translate-x`, `--translate-y`
- Size: `--width`, `--height`
- Spacing: `--padding-*`, `--margin-*`
- Border: `--border-*-width`, `--border-*-radius`
- Typography: `--font-size`, `--letter-spacing`, `--line-height`
- Effects: `--box-shadow-*`, `--text-shadow-*`, `--filter-blur`
- Content variants: `--content-font-size`, `--content-letter-spacing`, etc.
---
## Keybindings
Listen to built-in shortcuts, register custom ones, and list all keybindings in your UI.
The designer ships with a set of built-in keybindings for common actions like undo, redo, duplicate, and zoom. You can listen to those shortcuts, override them, and add your own — all with full type safety.
## Default Keybindings
The `DEFAULT_KEYBINDINGS` constant exposes the built-in shortcuts. Every keybinding follows the same shape:
| Property | Type | Description |
| --- | --- | --- |
| `key` | `string` | The key combination, written in [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/) syntax (e.g. `"mod+z"`). |
| `label` | `string` | The label shown for non-Mac platforms (e.g. `"Ctrl Z"`). |
| `labelMac` | `string` | The label shown on Mac (e.g. `"⌘ Z"`). |
| `description` | `string` | Human-readable description shown in the help dialog. |
| `group` | `string` | Group name used when listing shortcuts (e.g. `"History"`). |
Built-in groups include `History`, `Layer`, and `Zoom`. Each registered layer type also adds an `ADD_LAYER_` keybinding when its `keybinding` field is set.
## Listening to a Shortcut
Use `useShortcut` to run a callback when a keybinding fires. The hook accepts a `KeybindingName` — a typed union of all built-in names plus any custom or `ADD_LAYER_*` names you've registered:
```tsx
import { useShortcut } from "@shadcn/designer";
function ShortcutHandler() {
useShortcut("DUPLICATE_LAYER", () => {
console.log("Duplicate pressed");
});
useShortcut("DELETE_LAYER", () => {
console.log("Delete pressed");
});
return null;
}
```
If you mistype the name, TypeScript reports it instead of silently failing at runtime.
## Adding Custom Keybindings
You can extend or override the defaults in three steps:
### 1. Define your keybindings
Define your app's keybindings in a single file. Use `satisfies Record` to keep the literal keys narrow:
```ts
import { DEFAULT_KEYBINDINGS, type Keybinding } from "@shadcn/designer";
export const keybindings = {
...DEFAULT_KEYBINDINGS,
TOGGLE_PANEL_LEFT: {
key: "mod+b",
label: "Ctrl B",
labelMac: "⌘ B",
description: "Toggle left panel",
group: "Panels",
},
TOGGLE_PANEL_RIGHT: {
key: "mod+/",
label: "Ctrl /",
labelMac: "⌘ /",
description: "Toggle right panel",
group: "Panels",
},
} satisfies Record;
```
### 2. Pass them to the Designer
Pass your keybindings to `` so the runtime store knows about them:
```tsx
import { Designer } from "@shadcn/designer";
import { keybindings } from "./keybindings";
function MyDesigner() {
return (
{/* ... */}
);
}
```
### 3. Register them with TypeScript
Augment the `DesignerKeybindings` interface with `InferKeybindings` so `useShortcut` and `KeybindingName` know about your custom names:
```tsx
import type { InferKeybindings } from "@shadcn/designer";
import type { keybindings } from "./keybindings";
declare module "@shadcn/designer" {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface DesignerKeybindings extends InferKeybindings {}
}
```
See [Type Safety](/docs/concepts/type-safety) for more on the registry pattern.
After registration, your custom names autocomplete and type-check:
```tsx
useShortcut("TOGGLE_PANEL_LEFT", () => {
setLeftPanelOpen((open) => !open);
});
```
## Listing All Keybindings
Use `useKeybindings` to get the merged map of built-in, custom, and `ADD_LAYER_*` keybindings. This is useful for building a help dialog:
```tsx
import { type Keybinding, useKeybindings } from "@shadcn/designer";
import { useIsMac } from "@shadcn/designer/hooks";
import { useMemo } from "react";
export function KeyboardShortcuts() {
const keybindings = useKeybindings();
const isMac = useIsMac();
const groups = useMemo(() => {
return Object.values(keybindings).reduce(
(acc, keybinding) => {
acc[keybinding.group] = [...(acc[keybinding.group] ?? []), keybinding];
return acc;
},
{} as Record
);
}, [keybindings]);
return (
);
}
```
Group rows by `keybinding.group` to render sections, then pick the platform-appropriate label using `useIsMac`.
## Layer Keybindings
Layer types can declare their own keybinding alongside the layer definition. The designer registers it as `ADD_LAYER_` and wires it up to add a new layer when pressed:
```tsx
createLayerType({
type: "shape",
name: "Shape",
keybinding: {
key: "s",
label: "S",
labelMac: "S",
description: "Add shape",
group: "New Layer",
},
// ...
});
```
After registering the layer type, `useShortcut("ADD_LAYER_SHAPE", ...)` becomes a valid call and the entry appears in the merged map returned by `useKeybindings`.
## Next Steps
- See [Type Safety](/docs/concepts/type-safety) for more on the module augmentation pattern.
- Browse the [hooks reference](/docs/reference/hooks) for `useShortcut` and `useKeybindings`.
---
## Essential Hooks
The designer hooks you will reach for most often when building custom editor UI.
Most custom designer UI is built from a small set of hooks. The full
[hooks reference](/docs/reference/hooks) lists everything, but this page covers
the hooks that usually matter first when you are wiring up toolbars, panes,
custom controls, and document actions.
## Reading the document
Use these hooks when your UI needs to reflect the current document or selection.
| Hook | Use it for |
| --- | --- |
| `useLayers` | Reading the raw layer array. |
| `useLayersWithStyles` | Reading layers with computed `style` and `contentStyle`. |
| `useLayerTree` | Rendering a nested layer tree. |
| `useSelectedLayers` | Reading the selected layer objects. |
| `useSelectedLayerTypes` | Showing controls for the selected layer types. |
Most inspector controls start with `useSelectedLayers`:
```tsx
import { useSelectedLayers } from "@shadcn/designer"
function SelectedLayersList() {
const selectedLayers = useSelectedLayers()
if (!selectedLayers.length) {
return null
}
// Render a list of the selected layers.
return (
{selectedLayers.map((layer) => (
{layer.name}
))}
)
}
```
## Updating layer data
Layer data falls into four buckets: common layer properties, layer-specific
values, metadata, and CSS variables.
| Hook | Use it for |
| --- | --- |
| `useSetLayersValue` | Updating the `value` of one or more layers. |
| `useSetLayersMeta` | Updating the `meta` of one or more layers. |
| `useSetLayersProperty` | Updating shared layer fields like `name`, `isLocked`|
| `createLayerCssVarAction` + `useLayerCssVarAction` | Building controls for CSS variables like fill, size, position, and typography. |
Use `useSetLayersValue` for custom layer values:
```tsx
import { useSelectedLayers, useSetLayersValue } from "@shadcn/designer"
function ShapeTypeAction() {
const selectedLayers = useSelectedLayers()
const setLayersValue = useSetLayersValue()
const shapeLayers = selectedLayers.filter((layer) => layer.type === "shape")
return (
)
}
```
`useSetLayersValue` and `useSetLayersMeta` accept a layer ID, an array of layer
IDs, a layer object, or an array of layer objects. If you only have IDs, pass the
layer type as a generic so TypeScript can check the value:
```tsx
setLayersValue<"shape">(selectedLayerIds, { type: "square" })
setLayersMeta<"shape">(selectedLayerIds, { source: "toolbar" })
```
Use the updater form when the next value depends on the previous value:
```tsx
setLayersValue<"shape">("shape-1", (prev) => ({
...prev,
type: prev.type === "circle" ? "square" : "circle",
}))
```
Use `useSetLayersProperty` for common fields that are not specific to one layer
type:
```tsx
const setLayersProperty = useSetLayersProperty()
setLayersProperty(selectedLayerIds, "isLocked", true)
```
Do not use `useSetLayersProperty` to update `cssVars`. Use `createLayerCssVarAction` and `useLayerCssVarAction` instead.
For visual properties stored in `cssVars`, prefer a CSS variable action. It
automatically reads and writes the value across the current selection.
```tsx
import {
createLayerCssVarAction,
useLayerCssVarAction,
} from "@shadcn/designer"
const fill = createLayerCssVarAction("--background-color", "#000000")
function FillAction() {
const [value, setValue] = useLayerCssVarAction(fill)
return (
setValue(event.target.value)}
/>
)
}
```
All of these setters record undo history by default. Pass `{ history: false }`
when you need an internal update that should not create an undo step:
```tsx
setLayersValue<"shape">("shape-1", { type: "circle" }, { history: false })
```
## Creating and deleting layers
Use these hooks when a control changes the structure of the document.
| Hook | Use it for |
| --- | --- |
| `useAddLayers` | Creating one or more layers from layer input. |
| `useDeleteLayers` | Deleting layers by ID. |
Use `useAddLayers` when you are creating new layer objects yourself:
```tsx
const addLayers = useAddLayers()
addLayers([
{
type: "text",
name: "Text",
value: "Hello world",
cssVars: {
"--translate-x": "120px",
"--translate-y": "120px",
},
},
])
```
## Canvas and editor controls
These hooks control the designer shell rather than the document data itself.
| Hook | Use it for |
| --- | --- |
| `useDesignerAction` | Running canvas-level commands like zoom, undo, redo, redraw, and deselect. |
| `useDesignerTool` / `useSetDesignerTool` | Reading or switching between the move and hand tools. |
| `useZoom` / `useSetZoom` | Reading or setting the current zoom. |
| `useFrameSize` / `useSetFrameSize` | Reading or setting the frame size. |
| `useUnitSystem` / `useSetUnitSystem` | Reading or setting the active unit system. |
| `useDPI` / `useSetDPI` | Reading or setting DPI for unit conversion. |
`useDesignerAction` is useful for toolbar buttons that map directly to designer
commands:
```tsx
const designerAction = useDesignerAction()
designerAction("ZOOM_IN")
designerAction("ZOOM_FIT")
designerAction("UNSELECT_ALL")
```
For controls that need to show the current value, pair the read and write hooks:
```tsx
const zoom = useZoom()
const setZoom = useSetZoom()
setZoom(Math.min(zoom + 0.1, 3))
```
## History
Use the dedicated history hooks when you are building undo and redo UI.
| Hook | Use it for |
| --- | --- |
| `useUndo` | Undoing the last recorded layer change. |
| `useRedo` | Redoing the next recorded layer change. |
| `useCanUndo` | Disabling undo UI when there is nothing to undo. |
| `useCanRedo` | Disabling redo UI when there is nothing to redo. |
```tsx
function HistoryActions() {
const undo = useUndo()
const redo = useRedo()
const canUndo = useCanUndo()
const canRedo = useCanRedo()
return (
)
}
```
## Next Steps
- See the full [Hooks Reference](/docs/reference/hooks).
- Learn how to register typed layer values in [Type Safety](/docs/concepts/type-safety).
- Learn how to build custom layer types in [Layers](/docs/concepts/layers).
---
## Type Safety
Register your layer types so Layer, actions, panes, and default layers stay type-safe across your app.
The designer is built around your layer types. Once you define those layer types, TypeScript can use them to narrow `Layer`, validate layer values, and catch invalid layer names before they reach the canvas.
The goal is to write the shape of each layer once, in your `layerTypes` array, and let the rest of the designer infer from it.
## Registering Your Layer Types
Layer type registration has two parts:
1. Create and export the `layerTypes` array that you pass to ``.
2. Augment the `DesignerLayerTypes` interface with the inferred type map.
For example, if your app defines custom layers in `src/layers`, export the final array from a barrel file:
```ts
import { DEFAULT_LAYER_TYPES } from "@shadcn/designer";
import { shapeLayer } from "./shape";
export const layerTypes = [...DEFAULT_LAYER_TYPES, shapeLayer];
```
Then add a declaration file that connects that runtime array to the designer's TypeScript registry:
```tsx
import type { InferLayerTypes } from "@shadcn/designer";
import type { layerTypes } from "./layers";
declare module "@shadcn/designer" {
interface DesignerLayerTypes extends InferLayerTypes {}
}
```
This follows a common TypeScript library pattern: your app augments a registry interface, and the library reads from that registry everywhere else.
`declare module` requires an `interface` — type aliases can't be merged into an existing module. If your linter flags this as an empty interface, configure `@typescript-eslint/no-empty-object-type` with `allowInterfaces: "with-single-extends"` for declaration files.
Make sure the declaration file is included by your `tsconfig.json`. In most Vite, Next.js, and TypeScript apps, a `src/designer.d.ts` file is picked up automatically.
## Registering Keybindings
The same registry pattern is available for custom keybindings. Define your app's keybindings with the `Keybinding` type:
```ts
import { DEFAULT_KEYBINDINGS, type Keybinding } from "@shadcn/designer";
export const keybindings = {
...DEFAULT_KEYBINDINGS,
TOGGLE_PANEL_LEFT: {
key: "meta+b",
label: "Ctrl B",
labelMac: "⌘ B",
description: "Toggle Left Panel",
group: "Panels",
},
} satisfies Record;
```
Then add those names to the designer registry with `InferKeybindings`:
```tsx
import type { InferKeybindings, InferLayerTypes } from "@shadcn/designer";
import type { keybindings } from "./keybindings";
import type { layerTypes } from "./layers";
declare module "@shadcn/designer" {
interface DesignerLayerTypes extends InferLayerTypes {}
interface DesignerKeybindings extends InferKeybindings {}
}
```
After that, APIs like `useShortcut` and `` accept your custom keybinding names:
```tsx
useShortcut("TOGGLE_PANEL_LEFT", () => {
setLeftPanelOpen((open) => !open);
});
```
Layer keybindings are also typed automatically. If you register a `shape` layer, `ADD_LAYER_SHAPE` becomes a valid `KeybindingName`.
## What You Get
After registration, APIs in `@shadcn/designer` use your layer map:
- **`Layer.type`** becomes a union of literal strings, like `"frame" | "group" | "text" | "image" | "shape"`.
- **`Layer.value`** narrows based on `type` — TypeScript can discriminate values in `switch`/`if` statements.
- **`Layer.meta`** narrows based on `type` (when defined in `defaultValues`).
- **`showForLayerTypes`** only accepts valid type names — typos are caught at compile time.
- **`defaultLayers`** validates that each layer's `value` matches its `type`.
- **`KeybindingName`** includes built-in names, registered custom keybinding names, and `ADD_LAYER_*` names for registered layers.
For example, if your `shape` layer uses `value: { type: "square" }`, a simple type guard gives you the typed value:
```tsx
const layer = selectedLayers[0];
if (layer.type !== "shape") {
return null;
}
// layer.value is now typed as { type: string }.
console.log(layer.value.type);
```
The same registration also protects layer-specific UI:
```tsx
Shape
```
If you mistype `"shape"` here, TypeScript reports it instead of silently hiding the pane at runtime.
Layer value and meta setters use the same registry. If you pass narrowed layer
objects, the new value is checked against those layers:
```tsx
const shapeLayers = selectedLayers.filter((layer) => layer.type === "shape");
setLayersValue(shapeLayers, { type: "circle" });
```
If you only have IDs, pass the layer type as a generic:
```tsx
setLayersValue<"shape">(selectedLayerIds, { type: "square" });
setLayersMeta<"shape">(selectedLayerIds, { source: "toolbar" });
```
## InferLayerTypes
`InferLayerTypes` walks your `layerTypes` array and produces the registry map the designer needs:
```ts
type AppLayerTypes = InferLayerTypes;
```
Conceptually, that becomes:
```ts
type AppLayerTypes = {
shape: {
value: {
type: string;
};
};
};
```
You typically don't need to use it directly — the snippet above is the only place it appears in most apps.
## Common Issues
- If `Layer.type` is still `string`, check that `src/designer.d.ts` is included by TypeScript and restart your editor's TypeScript server.
- If custom layer names are missing from the union, make sure the same `layerTypes` array is exported from `src/layers` and passed to ``.
- Avoid annotating `layerTypes` as a wide `LayerType[]`; let TypeScript infer the array so literal `type` names are preserved.
## Performance Tip
If your project has a large number of layer types, prefer narrowing with type guards (`if (layer.type !== "shape") return`) over wide unions in function signatures. Narrowing early gives TypeScript less work to do and keeps editor performance snappy as your project grows.
## Next Steps
- See [Layers](/docs/concepts/layers) for the full walkthrough of creating a custom layer with type-safe values.
- Learn how to [use the unit system](/docs/concepts/unit-system).
- Browse the [type reference](/docs/reference/types) for `Layer`, `LayerType`, and related types.
---
## All Examples
See all examples of how to use the designer.
We have put together a few examples to help you get started with the designer. The examples cover a range of use cases and can be used as a starting point for your own projects.
---
## Basic Editor
A simple editor with a frame.
This is an example showing a basic editor with a frame inside a canvas. It has one `frame` layer.
> **Try it out:** Use your mouse to navigate, pan and zoom the canvas. Click on the blue frame to interact with it: move, resize, and rotate to see the editor in action.
> Configure Designer styles once in your app before using these examples. Use
> `@shadcn/designer/styles.css` for standalone apps, or
> `@shadcn/designer/core.css` with `@source "../node_modules/@shadcn/designer";`
> when your app already has Tailwind CSS 4.
## Components
We compose our custom editor using the following components:
- `Designer` to render the editor.
- `DesignerContent` as the container for the canvas and frame.
- `DesignerCanvas` for the infinite scrollable canvas.
- `DesignerFrame` to render the frame. By default, it's a 1024x1024px square.
```tsx
import {
Designer,
DesignerCanvas,
DesignerContent,
DesignerFrame,
} from "@shadcn/designer"
function CustomDesigner() {
return (
)
}
```
You can **create anything** from simple editors with just a frame to complex editors with headers, panels, toolbars, and more advanced features.
## Layer Types
The `Designer` includes **three built-in layer types**: `text`, `image`, and `frame`.
You can add **custom layer types** and remove the default ones using the `layerTypes` prop.
```tsx
import { DEFAULT_LAYER_TYPES, type LayerType } from "@shadcn/designer"
// Extends the default layer types with a custom video layer type.
const CUSTOM_LAYER_TYPES = [
...DEFAULT_LAYER_TYPES,
{
id: "video",
name: "Video",
// ... other props
},
] satisfies LayerType[]
function CustomDesigner() {
return (
)
}
```
## Working with Layers
The `Designer` component accepts a `defaultLayers` prop. It's an array of layers.
Use the `defaultLayers` prop to add default layers to the editor for example, layers loaded from a database.
```tsx
function CustomDesigner() {
return (
)
}
```
## Controlled Mode
The `Designer` component can be used in **controlled mode** by passing a `layers` and `onLayersChange` props.
```tsx
function CustomDesigner() {
const [layers, setLayers] = useState([])
return (
)
}
```
---
## Header
Add a header to the designer.
Use the `DesignerHeader` component to add a header to the designer.
The header is placed at the top of the designer and can contain custom component such as your logo, a search bar or editor controls.
```tsx
import {
Designer,
DesignerCanvas,
DesignerContent,
DesignerFrame,
DesignerHeader,
} from "@shadcn/designer"
import { Button } from "@shadcn/designer/ui"
function CustomDesigner() {
return (
studio
)
}
```
## Custom Header
Use the `className` prop to customize the header. For example, you can make it floating by adding `m-4 w-auto rounded-full border-0 shadow`.
```tsx
function CustomDesigner() {
return (
studio
)
}
```
---
## Panels
Add left and right panels to the designer.
You can have left and right panels by placing the `DesignerPanel` components before and after the `DesignerCanvas` component.
```tsx
Left PanelRight Panel
```
```tsx
import {
Designer,
DesignerCanvas,
DesignerContent,
DesignerFrame,
DesignerPanel,
} from "@shadcn/designer"
export function CustomDesigner() {
return (
Left Panel
Right Panel
)
}
```
---
## Panes
Add panes to the designer.
Use the `DesignerPane` component inside `DesignerPanel` to add panes to the designer.
A `DesignerPane` is a container for layer actions and controls. You can stack multiple panes inside a panel.
> **Try it out:** Click on _Hello World_ to select the text layer then use the _Font_ pane to edit it.
```tsx
"use client"
import {
ActionColor,
ActionFontStyle,
Designer,
DesignerCanvas,
DesignerContent,
DesignerFrame,
DesignerPane,
DesignerPaneContent,
DesignerPaneTitle,
DesignerPanel,
} from "@shadcn/designer"
export function CustomDesigner() {
return (
Right Panel
Font
)
}
```
---
## Toolbar
Add a toolbar to the designer.
You can use the `Toolbar` component to add a toolbar to the designer.
A toolbar is a container for toolbar buttons such as add layer, zoom, etc. You can group buttons together using the `DesignerToolbarGroup` component.
```tsx
import {
ActionToolbarAddLayer,
Designer,
DesignerCanvas,
DesignerContent,
DesignerFrame,
DesignerToolbar,
DesignerToolbarButton,
DesignerToolbarGroup,
DesignerToolbarSeparator,
} from "@shadcn/designer"
import { IconPlus } from "@tabler/icons-react"
import { toast } from "sonner"
function CustomDesigner() {
return (
toast("Custom Button Clicked")}
>
Button
)
}
```
## Toolbar Components
Use `DesignerToolbarGroup` to group toolbar buttons and `DesignerToolbarSeparator` to add a separator between groups.
```tsx
import { DesignerToolbarGroup, DesignerToolbarSeparator } from "@shadcn/designer"
function CustomToolbar() {
return (
)
}
```
You can use `DesignerToolbarButton` to add a button to the toolbar.
```tsx
import { DesignerToolbarButton } from "@shadcn/designer"
function CustomToolbarButton() {
return (
toast("Custom Button Clicked")}>
)
}
```
---
## Custom Toolbar
Build a custom toolbar for the designer.
Compose your components inside the `DesignerToolbar` component to create a custom toolbar.
- Use the `DesignerToolbarGroup` component to group your components and `DesignerToolbarSeparator` to add a separator between your components.
- For toolbar buttons, use the `DesignerToolbarButton` component.
```tsx
import {
ActionToolbarZoom,
DesignerToolbar,
DesignerToolbarButton,
DesignerToolbarGroup,
DesignerToolbarSeparator,
createLayerCssVarAction,
useLayerCssVarAction,
} from "@shadcn/designer"
import { IconCheck } from "@tabler/icons-react"
const backgroundColorAction = createLayerCssVarAction(
"--background-color",
"#3b82f6"
)
export function CustomToolbar() {
const [backgroundColor, setBackgroundColor] = useLayerCssVarAction(
backgroundColorAction
)
return (
{["#3b82f6", "#ef4444", "#22c55e", "#f59e0b", "#8b5cf6"].map(
(color) => (
setBackgroundColor(color)}
className="rounded-md bg-(--color) hover:bg-(--color) data-[active=true]:*:[svg]:opacity-100"
tooltip={color}
style={
{
"--color": color,
} as React.CSSProperties
}
>
{color}
)
)}
)
}
```
---
## Print & PDF
A print-ready editor with PDF export and paper size controls.
This example demonstrates a print-ready editor with support for standard paper sizes, orientations, DPI settings, and PDF export functionality.
> **Try it out:** Select a paper size and orientation, add text and images, then download your design as a high-quality PDF. Use the unit selector to work in pixels, millimeters, centimeters, inches, or points.
## Overview
The Print & PDF example showcases how to build a print-ready design editor with:
- Standard paper sizes (A4, A3, Letter, Legal, Tabloid)
- Portrait and landscape orientations
- DPI selection for print quality (96, 150, 300, 600 DPI)
- Multiple unit systems (px, mm, cm, in, pt)
- PDF export functionality
- Print-optimized canvas bounds
## Components
The editor uses a tabbed interface with two main sections:
- **Style Tab** - Layer styling controls for position, size, colors, typography, and more
- **Document Tab** - Paper size and orientation settings
This shows how to use your own custom components inside the designer to build advanced designer panels and toolbars.
```tsx
import {
Designer,
DesignerCanvas,
DesignerContent,
DesignerFrame,
DesignerPanel,
DesignerToolbar,
useSetFrameSize,
useDPI,
useSetDPI,
useUnitSystem,
useSetUnitSystem,
} from "@shadcn/designer"
function PrintEditor() {
return (
{/* Style and document controls */}
{/* Toolbar with unit, DPI, and export controls */}
)
}
```
## Unit System
The example supports multiple unit systems for precise print design.
For a detailed guide on working with unit systems, see the [Unit System](/docs/examples/unit-system) example.
```tsx
import { useUnitSystem, useSetUnitSystem } from "@shadcn/designer"
function UnitSelector() {
const unitSystem = useUnitSystem()
const setUnitSystem = useSetUnitSystem()
return (
)
}
```
## DPI Settings
Control the resolution of your print design with DPI selection:
```tsx
import { useDPI, useSetDPI } from "@shadcn/designer"
function DPISelector() {
const dpi = useDPI()
const setDPI = useSetDPI()
return (
)
}
```
## Print Bounds
The example uses `bounds` prop to define safe print margins:
```tsx
```
This ensures content stays within printable areas by showing visual guides at the specified margins.
## PDF Export
To export to PDF, we use the [`DesignerStaticFrame`](/docs/reference/designer#designerstaticframe) component to render a static, [read-only version](/docs/examples/read-only) of the layers.
Then we use the [`mql`](https://microlink.io/) package to generate a PDF of the static frame.
---
## Unit System
Work with different measurement units like pixels, mm, cm, in, and pt.
This example demonstrates how to use the unit system to display and work with measurements in different units.
> **Try it out:** Use the unit selector in the toolbar to switch between pixels (px), millimeters (mm), centimeters (cm), inches (in), and points (pt). Notice how the layer dimensions update in real-time. Change the DPI to see how it affects physical unit conversions.
## Overview
The Designer supports multiple unit systems, making it ideal for both screen and print design work.
See the [Print & PDF](/docs/examples/print) example for a complete print-ready editor that uses unit systems.
- **Pixels (px)** - For screen-based designs
- **Millimeters (mm)** - Common for print design in metric countries
- **Centimeters (cm)** - Larger metric measurements
- **Inches (in)** - Standard for print design in the US
- **Points (pt)** - Typography and print industry standard
## Unit System Selector
Add a unit selector to your toolbar to allow users to switch between units:
```tsx
import { useUnitSystem, useSetUnitSystem } from "@shadcn/designer"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@shadcn/designer/ui"
import type { Unit } from "@shadcn/designer/utils"
function UnitSystemSelector() {
const unitSystem = useUnitSystem()
const setUnitSystem = useSetUnitSystem()
return (
)
}
```
## DPI Settings
DPI (dots per inch) determines how physical units are converted to pixels. This is crucial for print design:
```tsx
import { useDPI, useSetDPI } from "@shadcn/designer"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@shadcn/designer/ui"
function DPISelector() {
const dpi = useDPI()
const setDPI = useSetDPI()
return (
)
}
```
## Converting Units
Use the `fromPixels` and `toPixels` utility functions to convert between units:
```tsx
import { fromPixels, toPixels } from "@shadcn/designer/utils"
// Convert pixels to another unit.
const mm = fromPixels(300, "mm", 300) // 25.4mm at 300 DPI
// Convert from another unit to pixels.
const pixels = toPixels(25.4, "mm", 300) // 300px at 300 DPI
```
## Displaying Dimensions
Show frame or layer dimensions in the current unit system:
```tsx
import { useUnitSystem, useDPI, useFrameSize } from "@shadcn/designer"
import { fromPixels } from "@shadcn/designer/utils"
function DimensionDisplay() {
const unitSystem = useUnitSystem()
const dpi = useDPI()
const frameSize = useFrameSize()
// Convert frame size to current unit.
const width = fromPixels(frameSize.width, unitSystem, dpi)
const height = fromPixels(frameSize.height, unitSystem, dpi)
// Format with appropriate precision.
const formatValue = (value: number) => {
if (unitSystem === "px") {
return Math.round(value)
}
return value.toFixed(2).replace(/\.?0+$/, "")
}
return (
)
}
```
## Setting the Default Unit System
Configure the default unit system when initializing the Designer:
```tsx
import { Designer, DesignerCanvas, DesignerFrame } from "@shadcn/designer"
function PrintDesigner() {
return (
)
}
```
## Unit System Hooks
The Designer provides several hooks for working with units:
### useUnitSystem
Get the current unit system:
```tsx
const unitSystem = useUnitSystem() // "px" | "mm" | "cm" | "in" | "pt"
```
### useSetUnitSystem
Change the unit system:
```tsx
const setUnitSystem = useSetUnitSystem()
setUnitSystem("mm")
```
### useDPI
Get the current DPI setting:
```tsx
const dpi = useDPI() // number (e.g., 300)
```
### useSetDPI
Change the DPI setting:
```tsx
const setDPI = useSetDPI()
setDPI(300)
```
## Common DPI Values
Different use cases require different DPI settings:
| DPI | Use Case |
|-----|----------|
| 72 | Low-resolution screen design (legacy) |
| 96 | Standard screen resolution |
| 150 | Draft print quality |
| 300 | Professional print quality (standard) |
| 600 | High-quality print / fine details |
## Unit Conversion Reference
Understanding unit conversions:
- **1 inch = 25.4 millimeters**
- **1 inch = 2.54 centimeters**
- **1 inch = 72 points** (PostScript/CSS standard)
- **Pixels depend on DPI**: At 300 DPI, 1 inch = 300 pixels
### Example Conversions at 300 DPI
| Physical Unit | Pixels |
|---------------|--------|
| 1 mm | ~11.81 px |
| 1 cm | ~118.11 px |
| 1 in | 300 px |
| 1 pt | ~4.17 px |
## Use Cases
### Screen Design
Use pixels (px) for web and app interfaces:
```tsx
{/* Your design */}
```
### Print Design
Use physical units (mm, cm, in) for print work:
```tsx
{/* Your design */}
```
### Typography
Use points (pt) for precise typography control:
```tsx
{/* Your design */}
```
## Notes
- All measurements are stored internally as pixels for consistency.
- Unit conversions happen on-the-fly when displaying or editing values.
- Changing DPI only affects physical units (mm, cm, in, pt), not pixels.
- The Action components (`ActionSize`, `ActionPosition`, etc.) automatically display values in the current unit system.
---
## Blob Editor
A custom svg blob editor built using the designer.
The following example shows how to build a custom svg blob editor using the designer.
We use custom layers and controls to create a blob editor that allows you to customize the blob's shape and fill color.
## Custom layers
The blob editor uses a custom layer type to render the blob. We use the `LayerType` type to define the layer type and its default values.
The `render` function is used to render the blob. It receives the layer and uses the layer custom values to generate the blob path.
```tsx
import { type LayerType } from "@shadcn/designer"
import { IconBlob } from "@tabler/icons-react"
const SVG_SIZE = 1024
// Define a blob layer type and its default values.
const blobLayer = {
name: "Blob",
type: "blob",
icon: ,
defaultValues: {
name: "Blob",
value: {
points: 5,
randomness: 0.5,
size: SVG_SIZE,
smoothness: 0.5,
},
cssVars: {},
},
render: (layer) => {
const blobPath = React.useMemo(() => {
return generateBlob(layer.value)
}, [layer.value])
return (
)
},
} satisfies LayerType
```
## Designer
Next, we create our custom designer.
The Designer component is the main component that renders the blob editor. It composes the `DesignerHeader`, `DesignerContent`, `DesignerCanvas`, and `DesignerFrame` components.
1. We pass the `blobLayer` to the `layerTypes` prop of the `Designer` component.
2. We add a default layer to the `layers` prop of the `Designer` component.
3. Since we're only working with one layer, we set the `singleLayerMode` prop to `true`.
```tsx
import { type Layer } from "@shadcn/designer"
const defaultLayers = [
{
id: "blob-1",
type: "blob",
name: "Blob",
isLocked: true,
value: {
points: 5,
randomness: 0.5,
size: SVG_SIZE,
smoothness: 0.5,
},
cssVars: {
"--width": `${SVG_SIZE}px`,
"--height": `${SVG_SIZE}px`,
"--fill": "#2151ef",
},
},
] satisfies Layer[]
function BlobEditor() {
return (
Blob Editor
)
}
```
## Custom controls
The blob editor uses custom controls to allow you to customize the blob's shape, size, and color.
We use the `Action` component to create custom controls. See the [Action](/docs/reference/ui#action) page for more information on how to use the `Action` component.
To see a list of built-in actions, see the [Actions](/docs/reference/actions) page.
## ActionBlobFill
The `ActionBlobFill` component is used to create a custom control for the blob's fill color. It uses the `useLayerCssVarAction` hook to get the current fill color and update it when the user selects a new color.
```tsx
import { useLayerCssVarAction, createLayerCssVarAction } from "@shadcn/designer"
const DEFAULT_FILL = "#2151ef"
const fillAction = createLayerCssVarAction("--fill", DEFAULT_FILL)
function ActionBlobFill() {
const [fill, setFill] = useLayerCssVarAction(fillAction)
return (
FillFill
)
}
```
## ActionBlobPoints
The `ActionBlobPoints` component controls the layer's `value.points` property. It uses the `` component to allow the user to select the number of points.
The `value` is updated using the `setLayersValue` function.
```tsx
import { useSelectedLayers, useSetLayersValue } from "@shadcn/designer"
import { Slider, Action, ActionLabel, ActionControls } from "@shadcn/designer/ui"
function ActionBlobPoints() {
const [selectedLayer] = useSelectedLayers()
const setLayersValue = useSetLayersValue()
if (!selectedLayer || selectedLayer.type !== "blob") {
return null
}
return (
Points
setLayersValue(selectedLayer, (prev) => ({
...prev,
points: value,
}))
}
/>
)
}
```
---
## Designer Tool
Switch between Move and Hand tools for different interaction modes.
The example shows how to use the `useDesignerTool` and `useSetDesignerTool` hooks to switch between **hand** mode and **move** mode.
Use the Cursor and Hand buttons in the toolbar to switch.
## Switching Tools Programmatically
Use the `useSetDesignerTool` hook to change tools programmatically:
```tsx
import { useDesignerTool, useSetDesignerTool } from "@shadcn/designer"
import { Button } from "@/components/ui/button"
function CustomToolSwitcher() {
const tool = useDesignerTool()
const setTool = useSetDesignerTool()
return (
)
}
```
## API Reference
For more details on the hooks, see the [Hooks Reference](/docs/reference/hooks#usedesignertool).
---
## Export Layers
How to export the layers as JSON.
The `useLayers` hook returns the layers object. It contains the layers and the styles for each layer at the current state.
```tsx
import { useLayers } from "@shadcn/designer"
const layers = useLayers()
```
You can use this to export the layers as JSON.
---
## Frame Size
How to change the size of the frame.
You can change the size of the frame by passing `frameSize` prop to the `` component.
```tsx
import { Designer, DesignerFrame } from "@shadcn/designer"
function CustomDesigner() {
return (
)
}
```
## useFrameSize
You can use the `useFrameSize` hook to get the current frame size.
```tsx
import { useFrameSize } from "@shadcn/designer"
function CustomDesigner() {
const { width, height } = useFrameSize()
return
Frame Size: {width}x{height}
}
```
## useSetFrameSize
Use the `useSetFrameSize` hook to set the frame size. It returns a function that you can call to set the frame size.
```tsx
import { useFrameSize, useSetFrameSize, useDesignerAction, Action, ActionControls, ActionLabel } from "@shadcn/designer"
function ActionFrameSize() {
const { width, height } = useFrameSize()
const setFrameSize = useSetFrameSize()
const designerAction = useDesignerAction()
const handleFrameSizeChange = (value: string) => {
const [width, height] = value.split("x")
if (!width || !height) {
return
}
setFrameSize({
width: Number.parseInt(width),
height: Number.parseInt(height),
})
designerAction("ZOOM_FIT")
}
return (
Frame Size
)
}
```
---
## History
How to use the history feature to undo and redo actions.
You can use the `useUndo` and `useRedo` hooks to undo and redo actions. We also provide `useCanUndo` and `useCanRedo` hooks to check if you can undo or redo.
> **Try it out:** Select, drag and resize the text layer. Then use the undo and redo buttons to undo and redo the actions.
```tsx
import { useUndo, useRedo, useCanUndo, useCanRedo } from "@shadcn/designer"
function HistoryControls() {
const canUndo = useCanUndo()
const canRedo = useCanRedo()
const undo = useUndo()
const redo = useRedo()
return (
)
}
```
## ActionToolbarHistory
The `ActionToolbarHistory` component is a toolbar button that allows you to undo and redo actions.
```tsx
import { Designer, DesignerToolbar, ActionToolbarHistory } from "@shadcn/designer"
function CustomDesigner() {
return (
)
}
```
---
## Image Generator
An AI image generator with filters.
This example shows how to use the image generator. It uses the [FAL API](https://fal.com/docs/api) to generate images.
We use custom components in the `DesignerToolbar` to show a prompt form, history controls and a cropper.
We've also added a custom `DesignerPanel` to show image filters.
> **Note**: Please note that the demo is limited to 5 image generations per day.
## Image Generator
For this example, we only need to use the `image` layer type. We use the `layerTypes` prop to filter out the other layer types.
Then, we create a single image layer and set the `value` to an empty string. We also set the `cssVars` to the default width and height.
We use `single` mode to only show a single image layer.
```tsx
function ExampleImageGenerator() {
return (
)
}
```
## Prompt Form
The prompt form displayes a text input in the toolbar. When we receive the image from the API, we update the layer `value` with the generated image and reset the crop.
```tsx
function ActionToolbarPromptForm() {
const [isLoading, setIsLoading] = React.useState(false)
const setLayersValue = useSetLayersValue()
const setLayersProperty = useSetLayersProperty()
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
const formData = new FormData(e.target as HTMLFormElement)
const prompt = formData.get("prompt") as string
if (!prompt.trim()) {
return
}
setIsLoading(true)
try {
// Make request to generate image.
const response = await fetch("/api/images/generate")
const data = await response.json()
if (!data.image) {
throw new Error("No image generated")
}
if (data.image) {
// Update layer with generated image and reset crop.
setLayersValue<"image">(["image-1"], `data:image/jpeg;base64,${data.image}`)
setLayersProperty(["image-1"], "cssVars", {
"--width": "1024px",
"--height": "1024px",
})
}
} catch {
toast.error("Something went wrong. Please try again.")
} finally {
setIsLoading(false)
}
}
return (
)
}
```
## Image Cropper
For the cropper, we use the built-in `ActionImageCropper` component.
```tsx
function ActionToolbarCropper() {
const { open, setOpen } = useActionImage()
return (
)
}
```
## Image Filters
To show filters, we create a custom action component that updates the layer `cssVars` with the filter values.
```tsx
const filters = [
{
name: "vibrant",
cssVars: {
"--filter-brightness": "115%",
"--filter-contrast": "130%",
"--filter-saturate": "250%",
"--filter-hue-rotate": "0deg",
},
},
{
name: "noir",
cssVars: {
"--filter-grayscale": "100%",
"--filter-brightness": "90%",
"--filter-contrast": "150%",
},
},
{
name: "vintage",
cssVars: {
"--filter-sepia": "60%",
"--filter-brightness": "110%",
"--filter-contrast": "80%",
"--filter-saturate": "120%",
},
},
{
name: "dreamy",
cssVars: {
"--filter-blur": "2px",
"--filter-brightness": "120%",
"--filter-saturate": "150%",
"--filter-contrast": "90%",
},
},
{
name: "cool",
cssVars: {
"--filter-hue-rotate": "200deg",
"--filter-brightness": "100%",
"--filter-contrast": "100%",
"--filter-sepia": "10%",
},
},
]
function ActionImageFilters() {
const layers = useLayers()
const setLayersProperty = useSetLayersProperty()
if (!layers[0]) {
return null
}
return (
{filters.map((filter) => (
))}
)
}
```
---
## Custom Layer
Add custom layer types to the designer.
You can add your own custom layer types to the designer. The following example shows how to add a custom `logo` layer type.
The `logo` layer type has a `value` property that can be used to store the logo name and a `render` function that can be used to render the logo.
> **Try it out:** Click on the logo to select it and pick a different one using the logo picker. You can also change the fill color of the logo using the fill action.
## Layer Type
Start by defining the layer type.
```tsx
import { type LayerType } from "@shadcn/designer"
// Logo data.
const LOGOS = [
{
name: "Default",
path: ``,
},
{
name: "Anthropic",
path: ``,
},
// ...
]
// Custom layer type.
const customLayerTypes = [
{
type: "logo",
name: "Logo",
defaultValues: {
name: "Logo",
value: "Default",
},
render: (layer) => {
const logo = LOGOS.find((logo) => logo.name === layer.value)
if (!logo) {
return null
}
return (
)
},
},
] satisfies LayerType[]
```
## Designer
Create the designer with a default layer. This will render a frame with a `logo` layer with the default logo.
```tsx
function CustomDesigner() {
return (
)
}
```
## ActionLogoPicker
To add a logo picker action, we can use the `
Select Logo
)
}
```
## ActionLogoFill
To control the fill color of the logo, we use `createLayerCssVarAction` to create an action that can be used to update the `fill` CSS variable.
```tsx
const DEFAULT_FILL = "#000000"
const fillAction = createLayerCssVarAction("--fill", DEFAULT_FILL)
function ActionLogoFill() {
const selectedLayers = useSelectedLayers()
const [fill, setFill] = useLayerCssVarAction(fillAction)
return (
Fill
)
}
```
---
## Layer Tree
Add a layer tree to the designer.
You can add a layer tree to display the layers in the designer.
Use the built-in `PaneLayerTree` component to show the layers in the designer. You can click to select, double click to edit, and use the custom lock controls to lock the layer.
```tsx
import { PaneLayerTree } from "@shadcn/designer"
function CustomDesigner() {
return (
Layers
)
}
```
## Custom Layer Tree
You can also create a custom layer tree by using the `useLayers` hook. The `useLayers` hook returns the layers in the designer.
```tsx
import { useLayers } from "@shadcn/designer"
function CustomLayerTree() {
const layers = useLayers()
return (
{layers.map((layer) => (
{layer.name}
))}
)
}
```
---
## Mobile
How to make the designer mobile friendly.
The following example shows how to make the designer mobile friendly.
Photo by [Polina Shirokova](https://unsplash.com/@aceafel?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash) on [Unsplash](https://unsplash.com/photos/a-woman-leaning-against-a-glass-wall-wearing-a-hat-qwKVxlx_mas?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash)
---
## Mode
Single or multiple layer mode.
The `Designer` component has a `mode` prop that can be set to `single` or `multiple`. The default is `multiple`.
In `single` mode, the designer is optimized for working with a single layer, making it perfect for focused editing tasks like image manipulation. This mode streamlines the interface and interactions by removing multi-layer selection capabilities and related controls, providing a more focused editing experience.
In `single` mode, the designer will automatically select the first layer when the designer is mounted.
> **Try it out:** Click on the _Toggle Grayscale_ button to toggle the grayscale filter on the image.
```tsx
import {
Designer,
DesignerCanvas,
DesignerContent,
DesignerFrame,
DesignerHeader,
createLayerCssVarAction,
useLayerCssVarAction,
} from "@shadcn/designer"
import { Button } from "@shadcn/designer/ui"
const grayscaleAction = createLayerCssVarAction("--filter-grayscale", "0")
function CustomDesigner() {
const [grayscale, setGrayscale] = useLayerCssVarAction(grayscaleAction)
return (
studio
)
}
```
---
## Pages
How to create multi-page designs.
The `` component is designed to be unopinionated about state management, making it easy to build multi-page editors.
The following example demonstrates how to implement a page-based workflow with persistent storage. Each page maintains its own set of layers, and you can seamlessly switch between pages using custom actions.
## Pages
We define a `Page` type to represent each page in the editor. A page has an `id`, a `name`, and a `layers` array.
```tsx
type Page = {
id: string
name: string
layers: Layer[]
}
```
We use Jotai to manage the state of the editor. We define a `pagesAtom` to store the list of pages, and a `selectedPageIdAtom` to store the ID of the currently selected page.
```tsx
const INITIAL_PAGES = [
{
id: "1",
name: "Page 1",
layers: [],
},
]
const pagesAtom = atomWithStorage("pages", INITIAL_PAGES)
const selectedPageIdAtom = atomWithStorage("selectedPageId", INITIAL_PAGES[0].id)
```
## Designer
The `` component is used to create the editor. We use the `layers` prop to pass the layers for the currently selected page and the `onLayersChange` prop to update the layers for the currently selected page.
```tsx
export function CustomDesigner() {
return (
)
}
```
## ActionPageSelector
```tsx
function ActionPageSelector() {
const [pages] = useAtom(pageAtom)
const [selectedPageId, setSelectedPageId] = useAtom(selectedPageIdAtom)
return (
Page
)
}
```
## ActionAddPage
```tsx
function ActionAddPage() {
const [pages, setPages] = useAtom(pageAtom)
const [, setSelectedPageId] = useAtom(selectedPageIdAtom)
return (
)
}
```
## ActionReset
```tsx
function ActionReset() {
const [, setPages] = useAtom(pageAtom)
const [, setSelectedPageId] = useAtom(selectedPageIdAtom)
return (
)
}
```
## ActionExportPages
```tsx
function ActionExportPages() {
const [pages] = useAtom(pageAtom)
return (
)
}
```
---
## Performance
Optimized for performance
We have optimized the designer for performance. No extra re-renders. Everything just works. Even with 100 layers.
---
## Read Only
How to make the designer read only.
Use the `DesignerStaticFrame` component to create a read only designer. This component is useful when you want to display a static image with all the layers.
> **Note:** The example below is rendered in full size. Pan around to see all the layers.
```tsx
import { DesignerStaticFrame } from "@shadcn/designer";
function ReadOnlyFrame() {
return (
)
}
```
See the [Convert to Image](/docs/guides/convert-image) example to see an example of how to export your layers to an image.
---
## Save to Database
This example shows how to save and load layers from a database.
Learn how to persist designer layers to a database and load them back for editing. We'll use Next.js server actions with a unified `upsertPost` function to handle both create and update operations.
> **Try it out:** Open the example below in a new tab and try it out. Open in new tab
## Overview
The save functionality provides a complete workflow:
1. **Create** - Save new designs and get a unique post ID
2. **Edit** - Load existing designs by post ID and make changes
3. **Update** - Save changes to existing posts while preserving metadata
4. **View** - Display saved designs in a static, read-only format
## ActionSave Component
The `ActionSave` component demonstrates how to save layers using the `useLayers` hook and server actions.
```tsx
"use client"
import { useLayers } from "@shadcn/designer"
import { Button } from "@shadcn/designer/ui"
import { useRouter } from "next/navigation"
import { useTransition } from "react"
import { toast } from "sonner"
import { upsertPost } from "./actions"
function ActionSave() {
const layers = useLayers()
const [isPending, startTransition] = useTransition()
const router = useRouter()
const handleSave = (e: React.FormEvent) => {
e.preventDefault()
startTransition(async () => {
const { error, postId } = await upsertPost(layers)
if (error) {
toast.error(error)
return
}
if (postId) {
router.push(`/examples/save/${postId}`)
}
})
}
return (
)
}
```
## upsertPost
> **Note:** We're using Redis (Upstash) as the database, but you can adapt this to work with any database (PostgreSQL, MongoDB, etc.).
The `upsertPost` function handles both creating new posts and updating existing ones.
```tsx
"use server"
import type { Layer } from "@shadcn/designer"
import { generateId } from "@shadcn/designer/utils"
import { Redis } from "@upstash/redis"
const redis = Redis.fromEnv()
export async function upsertPost(layers: Layer[], postId?: string) {
try {
// If postId provided, update existing post.
if (postId) {
const existing = await redis.get(`post:${postId}`)
if (!existing) {
return {
postId: null,
error: "Post not found",
}
}
await redis.set(`post:${postId}`, {
layers,
createdAt:
typeof existing === "object" && "createdAt" in existing
? existing.createdAt
: new Date().toISOString(),
updatedAt: new Date().toISOString(),
})
return {
postId,
error: null,
}
}
// Create new post.
const newPostId = generateId()
await redis.set(`post:${newPostId}`, {
layers,
createdAt: new Date().toISOString(),
})
return {
postId: newPostId,
error: null,
}
} catch (error) {
console.error("Failed to upsert post", error)
return {
postId: null,
error: error instanceof Error ? error.message : "Unknown error",
}
}
}
```
## Loading Saved Layers
To load an existing design for editing, create a dynamic route that fetches the layers and passes both the layers and postId to the editor:
```tsx
import { notFound } from "next/navigation"
import { getPost } from "../actions"
import { SaveExampleEditor } from "../editor"
export default async function SaveExampleViewPage(props: {
params: Promise<{
postId: string
}>
}) {
const params = await props.params
const layers = await getPost(params.postId)
if (!layers) {
notFound()
}
return
}
```
## getPost
```tsx
import { Redis } from "@upstash/redis"
const redis = Redis.fromEnv()
export async function getPost(postId: string) {
const entry = await redis.get<
{ layers: Layer[]; createdAt: string } | string
>(`post:${postId}`)
if (!entry) {
return null
}
if (typeof entry === "string") {
try {
const parsed = JSON.parse(entry) as {
layers: Layer[]
createdAt: string
}
return parsed.layers ?? null
} catch (error) {
console.error("Failed to parse stored layers", error)
return null
}
}
if (!Array.isArray(entry.layers)) {
return null
}
return entry.layers
}
```
## Static View
For a read-only preview, use the `DesignerStaticFrame` component:
```tsx
import { DesignerStaticFrame } from "@shadcn/designer"
export function SaveExampleViewStaticFrame({ layers }: { layers: Layer[] }) {
return
}
```
---
## Zoom
Zoom in and out controls and custom controls.
The designer ships with a `DesignerCanvas` component that is zoomable and panable. It has support for keyboard shortcuts and touch gestures.
Wrap your `DesignerFrame` component with the `DesignerCanvas` component to enable zooming and panning.
```tsx
```
## ActionToolbarZoom
Use the `ActionToolbarZoom` component to add zoom in and out controls to your designer.
```tsx
import {
ActionToolbarZoom,
DesignerToolbar,
DesignerToolbarGroup,
} from "@shadcn/designer"
function CustomToolbar() {
return (
)
}
```
## Keyboard Shortcuts
You can also use the keyboard shortcuts to zoom in and out.
- CMD or Ctrl + + to zoom in
- CMD or Ctrl + - to zoom out
- CMD or Ctrl + 0 to reset zoom
- CMD or Ctrl + 1 to zoom to fit
- CMD or Ctrl + 2 to zoom to 100%
## Custom Zoom Controls
You can bring your own zoom controls using the `useZoom` and `useSetZoom` hooks.
```tsx
function CustomZoomControls() {
const zoom = useZoom()
const setZoom = useSetZoom()
return (
)
}
```
## designerAction
You can also use the `designerAction` hook to set more controlled zoom levels.
This will handle maximum and minimum zoom levels.
```tsx
function CustomZoomControls() {
const designerAction = useDesignerAction()
return (
)
}
```
---
## Designer
The core components for building the design canvas and layout structure.
The designer provides a comprehensive set of React components for building design applications. These components handle everything from the core designer interface to individual property editors and UI primitives.
## Designer
The main container component that provides context and state management for the entire design interface.
The Designer component supports both **controlled** and **uncontrolled** patterns, similar to React form components.
## Uncontrolled
Use `defaultLayers` when you want the Designer to manage its own internal state. This is perfect for demos, static examples, and applications where you don't need external state management.
```tsx
import { Designer } from "@shadcn/designer"
function UncontrolledDesigner() {
return (
{
console.log("Designer mounted")
}}
>
{/* Designer content */}
)
}
```
## Controlled
Use `layers` + `onLayersChange` when you need full control over layer state.
```tsx
import { Designer } from "@shadcn/designer"
import { useState } from "react"
function ControlledDesigner() {
const [layers, setLayers] = useState([
{
id: "text-1",
type: "text",
name: "Hello",
value: "Hello World",
cssVars: {
"--width": "200px",
"--height": "100px",
}
}
])
const handleLayersChange = (newLayers) => {
setLayers(newLayers)
// Optionally save to external storage
saveToDatabase(newLayers)
}
return (
{/* Designer content */}
)
}
```
## Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `defaultLayers` | `Layer[]` | No* | Initial layers for uncontrolled mode |
| `layers` | `Layer[]` | No* | Current layers for controlled mode |
| `onLayersChange` | `(layers: Layer[]) => void` | No* | Callback for controlled mode |
| `layerTypes` | `LayerType[]` | No | Available layer types (defaults to built-in types) |
| `frameSize` | `{ width: number, height: number }` | No | Canvas frame size (default: 1024x1024) |
| `keybindings` | `Record` | No | Keyboard shortcuts configuration |
| `mode` | `"single" \| "multiple"` | No | Designer mode (default: "multiple") |
| `onMount` | `() => void` | No | Callback when designer is mounted |
| `debug` | `boolean` | No | Enable debug mode |
| `className` | `string` | No | Additional CSS classes |
**Important notes:**
- You can use one of `defaultLayers` (uncontrolled) or `layers` + `onLayersChange` (controlled), but not both.
- The `onLayersChange` callback is only called for user actions, not prop changes
- This prevents infinite loops when implementing controlled components
- Each pattern provides complete isolation - controlled components manage external state, uncontrolled components manage internal state
### mode
The `Designer` component has a `mode` prop that can be set to `single` or `multiple`. The default is `multiple`.
When in `single` mode, the designer is optimized for single layer editing, ideal for cases where you're only working with one layer for example an image editor.
In `single` mode, the designer will automatically select the first layer when the designer is mounted.
## DesignerHeader
Header area for toolbar controls and actions.
```tsx
import { Designer, DesignerHeader } from "@shadcn/designer"
function CustomDesigner() {
return (
{/* Header controls */}
)
}
```
## DesignerContent
Main content area that contains the canvas and panels.
```tsx
import { Designer, DesignerHeader, DesignerContent } from "@shadcn/designer"
function CustomDesigner() {
return (
{/* Panels and canvas */}
)
}
```
## DesignerCanvas
Canvas area where the design frame is rendered.
```tsx
import { Designer, DesignerContent, DesignerHeader, DesignerCanvas, DesignerFrame } from "@shadcn/designer"
function CustomDesigner() {
return (
)
}
```
## DesignerFrame
The interactive frame that contains and renders all layers.
```tsx
import { Designer, DesignerContent, DesignerHeader, DesignerCanvas, DesignerFrame } from "@shadcn/designer"
function CustomDesigner() {
return (
)
}
```
## DesignerStaticFrame
A static version of the frame for non-interactive rendering.
```tsx
import { DesignerStaticFrame } from "@shadcn/designer"
function PreviewFrame() {
return (
)
}
```
You can use this component to generate a static image of the design. See the [Convert to Image](/docs/guides/convert-image) guide for more information.
## DesignerPanel
Side panel container for organizing controls and tools. You can use one or both of these panels to render a designer with left and right panels.
```tsx
import { Designer, DesignerContent, DesignerHeader, DesignerCanvas, DesignerFrame, DesignerPanel, DesignerPane } from "@shadcn/designer"
function CustomDesigner() {
return (
{/* Left Panel */}
{/* Right Panel */}
)
}
```
## DesignerPane
Individual pane within a panel with a title and content. Use a pane to group related controls and tools. Panes can be configured to show only when specific layer types are selected.
```tsx
import { DesignerPane, DesignerPaneTitle, DesignerPaneContent, ActionImage } from "@shadcn/designer"
function PaneImageActions() {
return (
Image
)
}
```
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `showForLayerTypes` | `string[] \| "all"` | No | Controls when the pane is visible based on selected layer types. Defaults to `"all"`. |
```tsx
import { DesignerPane, DesignerPaneTitle, DesignerPaneContent } from "@shadcn/designer"
function ConditionalPanes() {
return (
<>
{/* Always visible (default) */}
General
{/* Only visible when text layers are selected */}
Text
{/* Only visible when image layers are selected */}
Image
{/* Visible when text OR icon layers are selected */}
Typography
>
)
}
```
**Available Layer Types:**
- `"frame"` - Frame/container layers
- `"text"` - Text layers
- `"image"` - Image layers
- Custom layer types defined in your `LayerType[]` configuration
**Visibility Rules:**
- `showForLayerTypes="all"` (default): Pane is always visible
- `showForLayerTypes={["text"]}`: Pane is visible only when text layers are selected
- `showForLayerTypes={["text", "image"]}`: Pane is visible when text OR image layers are selected
- The pane is visible only when **all** selected layers match the specified types
## DesignerToolbar
Toolbar container for grouping actions and controls. This component is displayed as a floating toolbar at the bottom of the designer.
```tsx
import { DesignerToolbar, DesignerToolbarGroup, DesignerToolbarButton, DesignerToolbarSeparator } from "@shadcn/designer"
import { IconArrowBackUp } from "@tabler/icons-react"
function CustomDesigner() {
return (
{}}>
)
}
```
- Use the `DesignerToolbarButton` component to add buttons to the toolbar.
- Use the `DesignerToolbarSeparator` component to add a separator between groups of buttons.
---
## Actions
Property editor components for modifying layer properties.
The `@shadcn/designer` package ships with a set of property editor components for modifying layer properties. We call these components "actions" because they are used to modify the properties of the selected layer.
See the [`Action`](/docs/reference/ui#action) component for more information on how to build your own actions.
## Layout & Positioning
### ActionSize
Controls for layer width and height.
```tsx
import { ActionSize } from "@shadcn/designer"
function SizeControls() {
return
}
```
### ActionPosition
Controls for layer X and Y position.
```tsx
import { ActionPosition } from "@shadcn/designer"
function PositionControls() {
return
}
```
### ActionRotate
Control for layer rotation.
```tsx
import { ActionRotate } from "@shadcn/designer"
function RotationControl() {
return
}
```
### ActionDirection
Control for flex direction property.
```tsx
import { ActionDirection } from "@shadcn/designer"
function DirectionControl() {
return
}
```
### ActionAlignItems
Controls for flex alignment properties.
```tsx
import { ActionAlignItems } from "@shadcn/designer"
function AlignmentControls() {
return
}
```
### ActionPadding
Controls for layer padding.
```tsx
import { ActionPadding } from "@shadcn/designer"
function PaddingControls() {
return
}
```
## Visual Styling
### ActionFill
Background color and fill controls.
```tsx
import { ActionFill } from "@shadcn/designer"
function FillControls() {
return
}
```
### ActionColor
Text color controls.
```tsx
import { ActionColor } from "@shadcn/designer"
function ColorControls() {
return
}
```
### ActionCorner
Border radius controls.
```tsx
import { ActionCorner } from "@shadcn/designer"
function CornerControls() {
return
}
```
### ActionBorder
Border style and width controls.
```tsx
import { ActionBorder } from "@shadcn/designer"
function BorderControls() {
return
}
```
### ActionBoxShadow
Box shadow controls.
```tsx
import { ActionBoxShadow } from "@shadcn/designer"
function ShadowControls() {
return
}
```
## Typography
### ActionFont
Font family picker with API integration.
```tsx
import { ActionFont } from "@shadcn/designer"
function FontPicker() {
return
}
```
**Props:**
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `apiUrl` | `string` | | API endpoint for font data |
### ActionFontSize
Font size control.
```tsx
import { ActionFontSize } from "@shadcn/designer"
function FontSizeControl() {
return
}
```
### ActionFontStyle
Font weight and style controls.
```tsx
import { ActionFontStyle } from "@shadcn/designer"
function FontStyleControls() {
return
}
```
### ActionLineHeight
Line height control.
```tsx
import { ActionLineHeight } from "@shadcn/designer"
function LineHeightControl() {
return
}
```
### ActionLetterSpacing
Letter spacing control.
```tsx
import { ActionLetterSpacing } from "@shadcn/designer"
function LetterSpacingControl() {
return
}
```
### ActionTextAlign
Text alignment controls.
```tsx
import { ActionTextAlign } from "@shadcn/designer"
function TextAlignControls() {
return
}
```
### ActionTextDecoration
Text decoration controls (underline, strikethrough).
```tsx
import { ActionTextDecoration } from "@shadcn/designer"
function TextDecorationControls() {
return
}
```
### ActionTextTransform
Text case transformation controls.
```tsx
import { ActionTextTransform } from "@shadcn/designer"
function TextTransformControls() {
return
}
```
### ActionTextShadow
Text shadow controls.
```tsx
import { ActionTextShadow } from "@shadcn/designer"
function TextShadowControls() {
return
}
```
### ActionTextStroke
Text stroke/outline controls.
```tsx
import { ActionTextStroke } from "@shadcn/designer"
function TextStrokeControls() {
return
}
```
### ActionTextValue
Text content editor.
```tsx
import { ActionTextValue } from "@shadcn/designer"
function TextEditor() {
return
}
```
## Image Controls
### ActionImage
Configurable image plugin container.
```tsx
import { ActionImage } from "@shadcn/designer"
function ImageControls() {
return (
)
}
```
**Props:**
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `plugins` | `string[]` | No | Array of enabled image plugins |
| `apiUrl` | `string` | No | API endpoint for image operations |
### ActionImageBrowser
Image browser with API integration.
```tsx
import { ActionImageBrowser } from "@shadcn/designer"
function ImageBrowser() {
return
}
```
### ActionImageCropper
Image cropping tool.
```tsx
import { ActionImageCropper } from "@shadcn/designer"
function ImageCropper() {
return
}
```
### ActionImageUrl
URL-based image input.
```tsx
import { ActionImageUrl } from "@shadcn/designer"
function ImageUrlInput() {
return
}
```
### ActionImageFit
Image fit and sizing controls.
```tsx
import { ActionImageFit } from "@shadcn/designer"
function ImageFitControls() {
return
}
```
### ActionImageFilter
Image filter controls.
```tsx
import { ActionImageFilter } from "@shadcn/designer"
function ImageFilterControls() {
return
}
```
---
## Toolbar
Components for building the floating designer toolbar.
These components are used to build the floating toolbar at the bottom of the designer. See also the [DesignerToolbar](/docs/reference/designer#designertoolbar) component in the [Designer](/docs/reference/designer) section.
## Toolbar Actions
### ActionToolbarAddLayer
Controls for adding new layers.
```tsx
import { ActionToolbarAddLayer, DesignerToolbar, DesignerToolbarGroup } from "@shadcn/designer"
function CustomDesignerToolbar() {
return (
)
}
```
### ActionToolbarHistory
Undo and redo controls.
```tsx
import { ActionToolbarHistory, DesignerToolbar, DesignerToolbarGroup } from "@shadcn/designer"
function CustomDesignerToolbar() {
return (
)
}
```
### ActionToolbarZoom
Zoom controls.
```tsx
import { ActionToolbarZoom, DesignerToolbar, DesignerToolbarGroup } from "@shadcn/designer"
function CustomDesignerToolbar() {
return (
)
}
```
---
## UI
Primitive UI components for building custom designer controls and actions.
## Action
Use the `Action` component to compose your own actions for consistent layouts and styles.
```tsx
import { Action, ActionControls, ActionLabel } from "@shadcn/designer/ui"
function CustomAction() {
return (
Width
)
}
```
## Button
```tsx
import { Button } from "@shadcn/designer/ui"
function ActionButton() {
return
}
```
## InputGroup
Wrap inputs and addons to build compact control clusters with optional scrubbing support.
- `InputGroup` positions addons around the focused input with consistent padding.
- `InputGroupAddon` can enable scrubbing, pulling min/max/step from nearby `InputNumber` fields.
- Use the `enableScrubbing`, `sensitivity`, and `acceleration` props to tune pointer adjustments.
```tsx
import * as React from "react"
import {
InputGroup,
InputGroupAddon,
InputNumber,
} from "@shadcn/designer/ui"
function SizeField() {
return (
W
)
}
```
## Input
```tsx
import { Input } from "@shadcn/designer/ui"
function TextInput() {
return
}
```
## InputNumber
Number input with increment/decrement controls.
```tsx
import { InputNumber } from "@shadcn/designer/ui"
function NumberInput() {
return
}
```
## Select
```tsx
import { Select } from "@shadcn/designer/ui"
function Dropdown() {
return (
)
}
```
## Textarea
```tsx
import { Textarea } from "@shadcn/designer/ui"
function MultilineInput() {
return
}
```
## Checkbox
```tsx
import { Checkbox } from "@shadcn/designer/ui"
function CheckboxInput() {
return Enable feature
}
```
## Slider
```tsx
import { Slider } from "@shadcn/designer/ui"
function RangeSlider() {
return
}
```
## ColorPicker
```tsx
import { ColorPicker } from "@shadcn/designer/ui"
function ColorSelector() {
return
}
```
## Tabs
```tsx
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@shadcn/designer/ui"
function TabNavigation() {
return (
Tab 1Tab 2Content 1Content 2
)
}
```
## Dialog
```tsx
import { Dialog } from "@shadcn/designer/ui"
function ModalDialog() {
return (
)
}
```
## Popover
```tsx
import { Popover } from "@shadcn/designer/ui"
function PopoverContent() {
return (
OpenPopover content
)
}
```
## DropdownMenu
```tsx
import { DropdownMenu } from "@shadcn/designer/ui"
function ContextDropdown() {
return (
MenuItem 1Item 2
)
}
```
## Tooltip
```tsx
import { Tooltip } from "@shadcn/designer/ui"
function HelpTooltip() {
return (
Hover meHelpful information
)
}
```
## Toggle
```tsx
import { Toggle } from "@shadcn/designer/ui"
function ToggleSwitch() {
return Toggle me
}
```
## ToggleGroup
```tsx
import { ToggleGroup } from "@shadcn/designer/ui"
function ToggleButtons() {
return (
LeftCenterRight
)
}
```
## Skeleton
Loading placeholder component.
```tsx
import { Skeleton } from "@shadcn/designer/ui"
function LoadingState() {
return
}
```
---
## Other
Other components available in the designer.
## ActionLayerMenu
Context menu for layer operations. Wrap your component in the `ActionLayerMenu` component to show the context menu.
```tsx
import { ActionLayerMenu } from "@shadcn/designer"
function LayerMenu() {
return (
)
}
```
## PaneLayerTree
Layer hierarchy tree view with drag-and-drop support.
```tsx
import { PaneLayerTree } from "@shadcn/designer"
function LayersPanel() {
return
}
```
---
## Hooks
The designer provides a comprehensive set of hooks for managing the state and behavior of the designer. These hooks provide a simple way to access and modify the designer's state, handle user interactions, and manage the design canvas.
## Designer
The core hooks for controlling the designer and the canvas.
### useDesignerAction
Executes designer-level actions like zoom, undo/redo, etc.
```tsx
import { useDesignerAction } from "@shadcn/designer"
function DesignerControls() {
const designerAction = useDesignerAction()
return (
)
}
```
**Available Actions:**
| Action | Description |
|--------|-------------|
| `ZOOM_IN` | Increase zoom level by one step |
| `ZOOM_OUT` | Decrease zoom level by one step |
| `ZOOM_RESET` | Reset zoom to default level and center canvas |
| `ZOOM_100` | Set zoom to 100% |
| `ZOOM_FIT` | Fit canvas to viewport and center |
| `REDRAW` | Redraw the canvas and center it |
| `UNSELECT_ALL` | Clear all layer selections |
| `HISTORY_UNDO` | Trigger undo action |
| `HISTORY_REDO` | Trigger redo action |
See also `useUndo` and `useRedo` for undo/redo functionality.
### useDesignerTool
Gets the currently active designer tool.
```tsx
import { useDesignerTool } from "@shadcn/designer"
function ToolIndicator() {
const tool = useDesignerTool()
return (
)
}
```
### useZoom
Gets the current zoom level of the canvas.
```tsx
import { useZoom } from "@shadcn/designer"
function ZoomIndicator() {
const zoom = useZoom()
return {Math.round(zoom * 100)}%
}
```
### useSetZoom
Sets the zoom level of the canvas.
```tsx
import { useSetZoom } from "@shadcn/designer"
function ZoomSlider() {
const zoom = useZoom()
const setZoom = useSetZoom()
return (
setZoom(parseFloat(e.target.value))}
/>
)
}
```
### useTargets
Gets the current interaction targets on the canvas.
```tsx
import { useTargets } from "@shadcn/designer"
function TargetInfo() {
const targets = useTargets()
return
Active targets: {targets.length}
}
```
### useSetTargets
Sets the interaction targets on the canvas.
```tsx
import { useSetTargets, type Target } from "@shadcn/designer"
function TargetController({ newTargets }) {
const setTargets = useSetTargets()
React.useEffect(() => {
const target = document.querySelector(
`[data-layer-id="SOME_LAYER_ID"].layer`
) as Target | null
setTargets([target])
}, [setTargets])
return null
}
```
## Layer Management
Essential hooks for working with layers - selecting, creating, modifying, and managing layer state.
### useSelectedLayers
Gets the currently selected layers on the canvas.
```tsx
import { useSelectedLayers } from "@shadcn/designer"
function LayerInfo() {
const selectedLayers = useSelectedLayers()
return (
{selectedLayers.length > 0 ? (
Selected: {selectedLayers[0].name}
) : (
No layers selected
)}
)
}
```
### useSetSelectedLayers
Sets the selected layers on the canvas.
```tsx
import { useSetSelectedLayers } from "@shadcn/designer"
function LayerSelector({ layer }) {
const setSelectedLayers = useSetSelectedLayers()
const handleSelect = () => {
setSelectedLayers([layer])
}
return
}
```
### useSelectedLayerIds
Gets an array of IDs for currently selected layers.
```tsx
import { useSelectedLayerIds } from "@shadcn/designer"
function SelectedLayerCount() {
const selectedLayerIds = useSelectedLayerIds()
return Selected: {selectedLayerIds.length}
}
```
### useSelectedLayerTypes
Gets the unique types of currently selected layers.
```tsx
import { useSelectedLayerTypes } from "@shadcn/designer"
function LayerTypeIndicator() {
const selectedLayerTypes = useSelectedLayerTypes()
return (
Types: {selectedLayerTypes.join(", ")}
)
}
```
### useLayers
Gets all layers in the design.
```tsx
import { useLayers } from "@shadcn/designer"
function LayerCount() {
const layers = useLayers()
return Total layers: {layers.length}
}
```
To add or remove layers, see [`useAddLayers`](#useaddlayers) and [`useDeleteLayers`](#usedeletelayers).
### useLayersWithStyles
Gets all layers with computed styles applied.
```tsx
import { useLayersWithStyles } from "@shadcn/designer"
function StyledLayerList() {
const layersWithStyles = useLayersWithStyles()
return (
{layersWithStyles.map(layer => (
{layer.name}
))}
)
}
```
### useAddLayers
Adds new layers to the design canvas.
```tsx
import { useAddLayers } from "@shadcn/designer"
function AddTextButton() {
const addLayers = useAddLayers()
const handleAddText = () => {
addLayers([
{
type: "text",
name: "New Text",
cssVars: {
"--content": "Hello World",
"--font-size": "16px"
}
}
])
}
return
}
```
### useDeleteLayers
Deletes layers from the design canvas.
```tsx
import { useDeleteLayers, useSelectedLayerIds } from "@shadcn/designer"
function DeleteSelectedButton() {
const deleteLayers = useDeleteLayers()
const selectedLayerIds = useSelectedLayerIds()
const handleDelete = () => {
deleteLayers(selectedLayerIds)
}
return (
)
}
```
### useGetLayers
Gets specific layers by their IDs.
```tsx
import { useGetLayers } from "@shadcn/designer"
function LayerDetails({ layerIds }) {
const getLayers = useGetLayers()
const layers = getLayers(layerIds)
return (
{layers.map(layer => (
{layer.name}
))}
)
}
```
### useSetLayersProperty
Sets a common layer property on multiple layers at once. Use this for shared
fields like `name`, `isLocked`, and `cssVars`. For layer-specific `value` and
`meta`, use [`useSetLayersValue`](#usesetlayersvalue) or
[`useSetLayersMeta`](#usesetlayersmeta) instead.
```tsx
import { useSetLayersProperty } from "@shadcn/designer"
function LockSelectedLayers() {
const setLayersProperty = useSetLayersProperty()
const selectedLayerIds = useSelectedLayerIds()
const handleLock = () => {
setLayersProperty(selectedLayerIds, "isLocked", true)
}
return
}
```
See the [Layer](/docs/reference/types#layer) type for more information on the properties that can be set and how to use the `meta` property to store arbitrary data on a layer.
### useSetLayersValue
Sets the `value` field on one or more layers. The target can be a layer ID, an
array of layer IDs, a layer object, or an array of layer objects. Pass either the
next value directly or an updater function that receives the previous value.
When the target is a narrowed layer object, the value is inferred from that layer
type:
```tsx
import { useSelectedLayers, useSetLayersValue } from "@shadcn/designer"
function ShapeTypeAction() {
const selectedLayers = useSelectedLayers()
const setLayersValue = useSetLayersValue()
const shapeLayers = selectedLayers.filter((layer) => layer.type === "shape")
return (
)
}
```
When the target is an ID or array of IDs, pass the layer type as a generic if you
want the value to be checked against a specific registered layer type:
```tsx
setLayersValue<"shape">(selectedLayerIds, { type: "square" })
```
Use an updater when the next value depends on the current value:
```tsx
setLayersValue<"shape">("shape-1", (prev) => ({
...prev,
type: prev.type === "circle" ? "square" : "circle",
}))
```
Pass `{ history: false }` as the third argument to skip recording the update in
undo history.
### useSetLayersMeta
Sets the `meta` field on one or more layers. It accepts the same targets,
updater form, and `{ history: false }` option as `useSetLayersValue`.
```tsx
import { useSelectedLayers, useSetLayersMeta } from "@shadcn/designer"
function MarkApprovedAction() {
const selectedLayers = useSelectedLayers()
const setLayersMeta = useSetLayersMeta()
const cardLayers = selectedLayers.filter((layer) => layer.type === "card")
return (
)
}
```
For ID targets, use the layer type generic to get the registered `meta` shape:
```tsx
setLayersMeta<"card">(selectedLayerIds, (prev) => ({
...prev,
approved: true,
}))
```
### useLayersAction
Executes common layer actions like duplicate, delete, show/hide, etc.
```tsx
import { useLayersAction, useSelectedLayerIds } from "@shadcn/designer"
function LayerActions() {
const layersAction = useLayersAction()
const selectedLayerIds = useSelectedLayerIds()
const handleDuplicate = () => {
layersAction("DUPLICATE_LAYER", selectedLayerIds)
}
const handleToggleVisibility = () => {
layersAction("SHOW_HIDE_LAYER", selectedLayerIds)
}
return (
)
}
```
**Available Actions:**
| Action | Description |
|--------|-------------|
| `DUPLICATE_LAYER` | Duplicate the selected layers |
| `SHOW_HIDE_LAYER` | Show or hide the selected layers |
| `BRING_TO_FRONT` | Bring the selected layers to the front |
| `SEND_TO_BACK` | Send the selected layers to the back |
| `LOCK_UNLOCK_LAYER` | Lock or unlock the selected layers |
### useLayerTypes
Gets the available layer types that can be created.
```tsx
import { useLayerTypes } from "@shadcn/designer"
function LayerTypeMenu() {
const layerTypes = useLayerTypes()
return (
)
}
```
### useLayerCssVarAction
Manages CSS variables for selected layers with type-safe serialization.
```tsx
// Create a typed CSS variable action
const fontSize = createLayerCssVarAction("--font-size", "16px")
```
You can also pass a `serialize` and `deserialize` function to the `createLayerCssVarAction` function to customize the serialization and deserialization of the value.
```tsx
const fontSize = createLayerCssVarAction("--font-size", "16px", {
serialize: (value: number) => `${value}px`,
deserialize: (value: string | undefined) => {
return value ? parseInt(value) : 16
}
})
```
```tsx
import { useLayerCssVarAction, createLayerCssVarAction } from "@shadcn/designer"
// Create a typed CSS variable action
const fontSize = createLayerCssVarAction("--font-size", "16px")
function FontSizeControl() {
const [value, setValue] = useLayerCssVarAction(fontSize)
return (
setValue(parseInt(e.target.value))}
/>
)
}
```
## History Management
Hooks for managing undo/redo functionality and action history.
### useUndo
Triggers an undo action.
```tsx
import { useUndo, useCanUndo } from "@shadcn/designer"
function UndoButton() {
const undo = useUndo()
const canUndo = useCanUndo()
return (
)
}
```
### useRedo
Triggers a redo action.
```tsx
import { useRedo, useCanRedo } from "@shadcn/designer"
function RedoButton() {
const redo = useRedo()
const canRedo = useCanRedo()
return (
)
}
```
### useCanUndo
Checks if undo is available.
```tsx
import { useCanUndo } from "@shadcn/designer"
function HistoryStatus() {
const canUndo = useCanUndo()
return canUndo ? "Changes available" : "No changes to undo"
}
```
### useCanRedo
Checks if redo is available.
```tsx
import { useCanRedo } from "@shadcn/designer"
function RedoStatus() {
const canRedo = useCanRedo()
return canRedo ? "Redo available" : "Nothing to redo"
}
```
## Keyboard Shortcuts
Hooks for handling keyboard interactions and custom shortcuts.
### useShortcut
Registers a keyboard shortcut with a callback.
```tsx
import { useShortcut } from "@shadcn/designer"
function ShortcutHandler() {
useShortcut("DUPLICATE_LAYER", () => {
console.log("Duplicate shortcut pressed")
})
useShortcut("DELETE_LAYER", () => {
console.log("Delete shortcut pressed")
})
return null // This component just handles shortcuts
}
```
### useKeybindings
Gets the current keybinding configuration.
```tsx
import { useKeybindings } from "@shadcn/designer"
function ShortcutList() {
const keybindings = useKeybindings()
return (
)
}
```
### useSetKeybindings
Sets custom keybinding configuration.
```tsx
import { useSetKeybindings } from "@shadcn/designer"
function CustomizeShortcuts() {
const setKeybindings = useSetKeybindings()
const handleCustomize = () => {
setKeybindings({
"DUPLICATE_LAYER": {
key: "cmd+d",
name: "Duplicate Layer",
group: "layer"
}
// ... more keybindings
})
}
return
}
```
## Development
Hooks for debugging and development.
### useDebug
Gets the current debug mode state.
```tsx
import { useDebug } from "@shadcn/designer"
function DebugPanel() {
const debug = useDebug()
if (!debug) return null
return
Debug information...
}
```
### useSetDebug
Sets the debug mode state.
```tsx
import { useSetDebug } from "@shadcn/designer"
function DebugToggle() {
const debug = useDebug()
const setDebug = useSetDebug()
return (
)
}
```
## Unit System
Hooks for managing measurement units and DPI settings. See the [Unit System](/docs/concepts/unit-system) guide for detailed usage.
### useUnitSystem
Gets the current unit system being used for display.
```tsx
import { useUnitSystem } from "@shadcn/designer"
function UnitDisplay() {
const unitSystem = useUnitSystem() // "px" | "mm" | "in" | "cm" | "pt"
return Current unit: {unitSystem}
}
```
### useSetUnitSystem
Sets the unit system for displaying values.
```tsx
import { useSetUnitSystem } from "@shadcn/designer"
function UnitSwitcher() {
const setUnitSystem = useSetUnitSystem()
return (
)
}
```
### useDPI
Gets the current DPI (dots per inch) setting used for unit conversions.
```tsx
import { useDPI } from "@shadcn/designer"
function DPIIndicator() {
const dpi = useDPI() // Default: 96
return DPI: {dpi}
}
```
### useSetDPI
Sets the DPI for unit conversions. Common values are 96 for screen and 300 for print.
```tsx
import { useSetDPI } from "@shadcn/designer"
function DPIControl() {
const setDPI = useSetDPI()
return (
)
}
```
## Utilities
Helper hooks for cross-platform compatibility and general utilities.
### useIsMac
Detects if the user is on macOS platform, useful for showing platform-specific keyboard shortcuts and UI elements.
```tsx
import { useIsMac } from "@shadcn/designer"
function ShortcutDisplay({ keybinding }) {
const isMac = useIsMac()
const label = isMac
? keybinding.labelMac
: keybinding.label
return {label}
}
```
### useSharedValues
Derives a single shared value from a list of numeric values. This is useful for grouped controls such as padding, radius, or border width where a collapsed input should only show a value when every side matches.
Pass a flat array for a single layer or nested arrays for multiple selected layers. When any value differs, `value` is `undefined` and `isMixed` is `true`, so the control can render a mixed state instead of showing an arbitrary side value.
```tsx
import { useSharedValues } from "@shadcn/designer"
function PaddingControl({
values,
onValueChange,
}: {
values: Array
onValueChange: (value: number) => void
}) {
const { value, isEqual, isMixed, isEmpty, setValue } = useSharedValues(
values,
onValueChange
)
return (
setValue(Number(event.target.value))}
/>
)
}
```
**Returns:**
| Property | Description |
|----------|-------------|
| `value` | The shared number when all values match, otherwise `undefined` |
| `isEqual` | `true` when every value is the same |
| `isMixed` | `true` when the values differ |
| `isEmpty` | `true` when every value is `0` |
| `setValue` | Calls `onValueChange` with the new shared value |
---
## Types
TypeScript type reference for the designer.
This page documents the public types exported from `@shadcn/designer`. For tutorials and conceptual overviews, see the [Concepts](/docs/concepts/layers) section.
## Layer
A layer on the canvas. `Layer` is a discriminated union — `type`, `value`, and `meta` narrow per registered layer type. Without [module augmentation](/docs/concepts/type-safety), `type` is `string` and `value` is `unknown`. Once augmented, the union narrows to your registered layer types.
```tsx
type Layer = {
id: string
name: string
type: LayerTypeName
value: unknown
cssVars?: CSSVars
meta?: Record
isLocked?: boolean
parentId?: string
}
```
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `id` | `string` | Yes | Unique identifier for the layer. |
| `name` | `string` | Yes | Display name. |
| `type` | `LayerTypeName` | Yes | Discriminator narrowed per registered layer type. |
| `value` | Inferred from layer type | Yes | Layer-specific content. |
| `cssVars` | `CSSVars` | No | CSS custom properties for styling. |
| `meta` | Inferred or `Record` | No | Arbitrary metadata. Narrows per layer type when augmented. |
| `isLocked` | `boolean` | No | Whether the layer is locked from editing. |
| `parentId` | `string` | No | ID of the parent layer (for grouping). |
## LayerWithStyles
`Layer` plus computed CSS for rendering. This is what `LayerType.render` receives.
```tsx
type LayerWithStyles = Layer & {
style: CSSProperties
contentStyle: CSSProperties
}
```
| Property | Type | Description |
|----------|------|-------------|
| `style` | `CSSProperties` | Computed styles for the layer container. |
| `contentStyle` | `CSSProperties` | Computed styles for the layer content (derived from `cssVars`). |
## LayerType
Configuration that defines how a layer type behaves and renders.
```tsx
type LayerType = {
type: LayerTypeName
name: string
defaultValues: DistributiveOmit
render: (layer: LayerWithStyles) => React.ReactNode
icon?: ReactNode
keybinding?: Keybinding
unstable_transformerProps?: (context: {
layer: Layer
context: TransformerContext
}) => Partial
}
```
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `type` | `LayerTypeName` | Yes | Unique type identifier. |
| `name` | `string` | Yes | Human-readable name. |
| `defaultValues` | `DistributiveOmit` | Yes | Default values for new layers. |
| `render` | `(layer: LayerWithStyles) => React.ReactNode` | Yes | Render function. |
| `icon` | `ReactNode` | No | Icon shown in the toolbar. |
| `keybinding` | `Keybinding` | No | Keyboard shortcut for adding the layer. |
| `unstable_transformerProps` | function | No | Customize transform behavior. **Internal API — subject to change.** |
Prefer [`createLayerType`](#createlayertype) over annotating with `LayerType` directly — it preserves literal `type`, `value`, and `meta` types.
## TypedLayerType
Return type of [`createLayerType`](#createlayertype). Preserves the literal type name `K`, value type `V`, and meta type `M`.
```tsx
type TypedLayerType<
K extends string = string,
V = unknown,
M extends Record = Record,
> = {
type: K
name: string
icon?: ReactNode
defaultValues: {
name: string
value: V
cssVars?: CSSVars
meta?: M
isLocked?: boolean
}
render: (layer: LayerWithStylesFor) => React.ReactNode
keybinding?: Keybinding
unstable_transformerProps?: (context: { layer: LayerFor; context: TransformerContext }) => Partial
}
```
## DesignerLayerTypes
Registry interface for type-safe layer definitions. Empty by default — augment via `declare module` to register your custom layer types.
```tsx
// Empty by default.
interface DesignerLayerTypes {}
```
```tsx
declare module "@shadcn/designer" {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface DesignerLayerTypes extends InferLayerTypes {}
}
```
When augmented, `Layer.type` becomes a union of your registered names, `Layer.value` narrows per type, and `Layer.meta` narrows per type. See [Type Safety](/docs/concepts/type-safety).
## LayerTypeName
Resolves to `string` when the registry is empty, or to a union of registered names when augmented.
```tsx
type LayerTypeName = keyof DesignerLayerTypes extends never
? string
: Extract
```
## LayerByType
Extracts the layer shape for a registered layer type. This is useful when you
want to type a helper or a user-defined type guard around a specific layer type.
```tsx
type LayerByType = Extract<
Layer,
{ type: TType }
> extends never
? LayerFor
: Extract
```
```tsx
function isShapeLayer(layer: Layer): layer is LayerByType<"shape"> {
return layer.type === "shape"
}
```
## LayerValue
Extracts the `value` type for a registered layer type. Used by
`useSetLayersValue`.
```tsx
type LayerValue = LayerByType["value"]
```
## LayerMeta
Extracts the `meta` type for a registered layer type. Used by
`useSetLayersMeta`.
```tsx
type LayerMeta = LayerByType["meta"]
```
## InferLayerTypes
Utility type that extracts layer type definitions from a `layerTypes` array for module augmentation.
```tsx
type InferLayerTypes<
T extends readonly { type: string; defaultValues: { value: unknown } }[],
> = {
[E in T[number] as E["type"]]: E["defaultValues"] extends { meta?: infer M }
? [M] extends [undefined]
? { value: E["defaultValues"]["value"] }
: { value: E["defaultValues"]["value"]; meta: NonNullable }
: { value: E["defaultValues"]["value"] }
}
```
```tsx
const layerTypes = [
createLayerType({ type: "text", name: "Text", defaultValues: { name: "Text", value: "Hello" }, render: () => null }),
createLayerType({ type: "card", name: "Card", defaultValues: { name: "Card", value: "", meta: { author: "" } }, render: () => null }),
]
// InferLayerTypes = {
// text: { value: string };
// card: { value: string; meta: { author: string } }
// }
```
## DistributiveOmit
A distributive version of `Omit` that preserves discriminated unions. Used internally by `LayerType.defaultValues`.
```tsx
type DistributiveOmit = T extends unknown ? Omit : never
```
## createLayerType
Helper function that creates a typed layer type definition with narrowed `render` and `defaultValues`. Infers `value` and `meta` from `defaultValues`.
```tsx
import { createLayerType } from "@shadcn/designer"
const textLayer = createLayerType({
type: "text",
name: "Text",
defaultValues: { name: "Text", value: "Hello" },
render: (layer) => {layer.value}, // layer.value is string.
})
```
See [Layers](/docs/concepts/layers) for the full walkthrough.
## DesignerKeybindings
Registry interface for custom keybinding names. Empty by default — augment via `declare module` to add custom names that get autocomplete in `useShortcut`.
```tsx
// Empty by default.
interface DesignerKeybindings {}
```
```tsx
declare module "@shadcn/designer" {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface DesignerKeybindings extends InferKeybindings {}
}
```
See [Keybindings](/docs/concepts/keybindings).
## KeybindingName
Union of all valid keybinding names. Includes built-in names from `DEFAULT_KEYBINDINGS`, dynamic `ADD_LAYER_` names from registered layer types, and any custom names registered via `DesignerKeybindings`.
```tsx
type KeybindingName = keyof DesignerKeybindings extends never
? BuiltinKeybindingName | AddLayerKeybindingName
: BuiltinKeybindingName | AddLayerKeybindingName | Extract
```
Used by `useShortcut`, `Shortcut`, and the `keybindings` prop on `Designer`.
## InferKeybindings
Utility type that extracts keybinding names from a keybindings object for module augmentation.
```tsx
type InferKeybindings> = {
[K in keyof T]: true
}
```
## Keybinding
Configuration for a keyboard shortcut.
```tsx
type Keybinding = {
key: string
label: string
labelMac: string
description: string
group: string
}
```
| Property | Type | Description |
|----------|------|-------------|
| `key` | `string` | Key combination in [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/) syntax (e.g. `"mod+d"`). |
| `label` | `string` | Display label for non-Mac platforms. |
| `labelMac` | `string` | Display label for Mac. |
| `description` | `string` | Human-readable description shown in the help dialog. |
| `group` | `string` | Group name used when listing shortcuts. |
## LayersActionName
Union of layer-specific action names dispatched via `useLayersAction`.
```tsx
type LayersActionName =
| "DUPLICATE_LAYER"
| "DELETE_LAYER"
| "SHOW_HIDE_LAYER"
| "BRING_TO_FRONT"
| "SEND_TO_BACK"
| "LOCK_UNLOCK_LAYER"
| "GROUP_LAYERS"
| "UNGROUP_LAYERS"
```
## DesignerActionName
Union of designer-level action names dispatched via `useDesignerAction`.
```tsx
type DesignerActionName =
| "ZOOM_IN"
| "ZOOM_OUT"
| "ZOOM_RESET"
| "ZOOM_100"
| "ZOOM_FIT"
| "REDRAW"
| "UNSELECT_ALL"
| "HISTORY_UNDO"
| "HISTORY_REDO"
```
## CSSVars
CSS custom properties used in layer styling.
```tsx
type CSSVars = Record
```
## FrameSize
Canvas frame dimensions.
```tsx
type FrameSize = {
width: number
height: number
unit?: Unit
}
```
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `width` | `number` | Yes | Frame width. |
| `height` | `number` | Yes | Frame height. |
| `unit` | `Unit` | No | Unit of measurement. Defaults to `"px"`. |
## Unit
Supported measurement units.
```tsx
type Unit = "px" | "mm" | "in" | "cm" | "pt"
```
| Unit | Description | Common Use Case |
|------|-------------|-----------------|
| `px` | Pixels | Screen designs, web layouts. |
| `mm` | Millimeters | Print designs. |
| `in` | Inches | Print designs. |
| `cm` | Centimeters | Print designs. |
| `pt` | Points | Typography. |
See [Unit System](/docs/concepts/unit-system) for conversion utilities and DPI handling.