## v0.4.0

We've released a new version of `@shadcn/designer` with several improvements to the image browser, a new image cropper and layer locking.

### Image Browser

![Image Browser](/images/pv3hRvKM.png)

The new `ActionImage` now has support for plugins. You can build your own image plugin and use it in the image browser.

```tsx
<ActionImage
  plugins={[
    {
      id: "cropper",
      label: "Crop",
      description:
        "Move and scale the image to crop it. The white frame represents the layer.",
      component: <ActionImageCropper />,
    },
    {
      id: "browser",
      label: "Browse",
      description:
        "Search and select an image to set as the image for the selected layer.",
      component: (
        <ActionImageBrowser
          apiUrl="/api/images"
        />
      ),
    },
  ]}
/>
```

See the [Image Browser](/docs/guides/image-browser) guide to learn how to build your own image browser plugin.

### Image Cropper

We've added a new `ActionImageCropper` component for image cropping.

![Image Cropper](/images/UGHEUXXN.png)

The `ActionImageCropper` can be used on its own or as a plugin for the `ActionImage` component.

### Locked Layers

We've also added a new `locked` property to layers. This allows you to lock a layer in place.

You can use the `layersAction("LOCK_UNLOCK_LAYER", [layer.id])` action to lock and unlock layers.

```tsx
import { useLayersAction } from "@shadcn/designer"

export function LockLayerButton({ layer }: { layer: DesignerLayer }) {
  const layersAction = useLayersAction()

  return (
   <Button onClick={() => layersAction("LOCK_UNLOCK_LAYER", [layer.id])}>
      {layer.isLocked ? <IconLock /> : <IconLockOpen />}
      <span className="sr-only">Toggle lock</span>
    </Button> 
  )
}

```

## v0.3.0

We've released a new version of `@shadcn/designer` with several improvements to the core functionality and user experience.

### History API

We've implemented a new History API that allows you to undo and redo changes in your designs. The API is available through three new hooks:

```tsx
// Get access to the history state
const history = useHistory()

// Undo the last change
const undo = useUndo()

// Redo the last undone change
const redo = useRedo()
```

### Improvements

We've made several improvements to the canvas interaction:

- Added support for wheel and pinch zoom gestures
- Fixed adaptive zoom behavior for better viewport handling
- Resolved keyboard shortcut issues related to zoom controls
- Fixed layer update flickering issues for smoother rendering

### What's Next?

We are building more examples and documentation to help you, including image generation with AI. Stay tuned!

## v0.2.0

We just tagged a new release of `@shadcn/designer`. This release brings a lot of new features and improvements.

### Font Picker

We've added a Google Font picker to the designer. 

The picker is decoupled from the designer so that you're free to implement your own picker or use the one we provide.

![Font Picker](/images/font-picker-light.png)
![Font Picker](/images/font-picker-dark.png)

### Image Browser

We've also added an image browser using the Unsplash API. Use this as a reference to build your own image picker.

![Image Browser](/images/image-browser-light.png)
![Image Browser](/images/image-browser-dark.png)

### Snapping

We've added snapping to the canvas. This means that layers will snap to the nearest grid point when you move them. This makes it easier to align layers to the canvas. We've also added distance guidelines for horizontal and vertical snapping.

![Snapping](/images/snapping-light.png)
![Snapping](/images/snapping-dark.png)

### Template Updates

- We've updated the template to use the new font picker and image browser.
- We added examples for fetching fonts and images from the Unsplash API.
- The template now ships with a _Playground_ to test image layers. This is the same playground that you see in the demo.
- We also added an _Inspector_ to test the static frame rendering. This is useful for turning posts into images.

That's it for now. Grab the [latest template](/docs#download-template) to see the new features. See the [Roadmap](/docs/roadmap) for what's coming next.

## v0.1.0

We are excited to bring you this update as we are getting close to General Availability (GA). 

This update brings a lot of changes to the core system. We've added a new set of hooks and components to make it easier to extend and build custom designers.

There are still a few things we're wrapping up, but we're excited to finally show you a demo of what we've been working on.

### Designer Demo

First, let's take a look at a demo of what we've been working on. Click on Launch Demo in the header to create your own personalized playground to test the editor.

Use this playground to test the editor features, layers, controls and actions.

### API

We've also built a demo to show how you can turn any design into an API. Using the API turns your designs into templates that can be used to generate designs for different contexts by passing in different layer values.

[Try the API](https://ds.shadcn.com/demo/dito/j73pj2qqr6nwdrs7/api)

### Export to Image

When you're done customizing your layer values, click the Download button to download the design as an image.

![Export to Image](/images/bGL6tRkr.png)

This can be used to generate images for social media, banners, etc and serve them on demand over a CDN.

### Performance Improvements

We've made a lot of performance improvements. 

We reworked the internal system to have better rendering performance. Updates are granular and only trigger when necessary. We also fixed an issue with dropped frames.

### Layer Types

You can now define your own layer types and override the default ones. A layer type can bring in its own keybindings, default values and render method.

```tsx
<Designer layerTypes={[
  {
    type: "custom",
    name: "Custom",
    icon: IconCustom,
    defaultValues: {
      // ...
    },
    keybinding: {
      // ...
    },
    render: (layer) => (
      // ...
    ),
  }
]} />
```

### Hooks

Every state and action are now available as hooks. We provide all the necessary hooks to read and update the state of the designer.

```tsx
// Get the layers.
const layers = useLayers()

// Set the zoom level
const designerAction = useDesignerAction()
designerAction("ZOOM_IN")

// Get the selected layers
const selectedLayers = useSelectedLayers()

// Change the name of a layers
const setLayersProperty = useSetLayersProperty()
setLayersProperty([ID_OF_LAYERS], "name", "New Name")
```

See the [reference](/docs/reference/hooks) for more information on available hooks.

### Frame Size

We've made the frame size customizable. You can now pass a `frameSize` prop to the `<Designer />` component to set the size of the frame. Useful for building social media images, banners, etc of different sizes.

```tsx
<Designer frameSize={{ width: 1024, height: 1024 }} />
```

To programmatically change the frame size, you can use the `setFrameSize` hook.

```tsx
const setFrameSize = useSetFrameSize()
setFrameSize({ width: 1024, height: 1920 })
```

### Keyboard Shortcuts

We've added a new `keybindings` prop to the `<Designer />` component to handle keyboard shortcuts. You can pass a `keybindings` object to the component to provide your own keybindings or override the default ones.

```tsx
<Designer keybindings={{
  DUPLICATE_LAYER: {
    key: "meta+d",
    label: "Ctrl D",
    labelMac: "⌘ D",
    description: "Duplicate selected layers",
    group: "Layer",
  },
}} />
```

See the [reference](/docs/reference/keybindings) for more information on available keybindings.

### Debug Mode

We've also added a new `debug` prop to the `<Designer />` component to enable debug mode during development.

    Previous Releases

## v0.0.1

See the [latest documentation](/docs) for the most up-to-date information.

Based on your valuable feedback, we've completely rebuilt [Designer](https://ds.shadcn.com) from the ground up. We would like to share an early preview that introduces a new level of composability and flexibility.

We've redesigned the system to be highly composable while preserving flexibility. This means you can build a simple component designer to a full-fledged designer with drag-and-drop functionality.

We're working on more documentation and examples, but we want to share an early preview with you. You can install and try it today.

Let's get started.

### Project Setup

We've put together a template to get you started. The template is a simple Vite app with Tailwind CSS, shadcn/ui and Designer.

Download the template and extract it into your project folder.

```bash
cd studio && pnpm install
```

```bash
pnpm dev
```

If you visit [http://localhost:5173](http://localhost:5173), you should see a white page displaying "Hello World".

![Hello World](/images/01.png)

### Setup the Designer

We'll start by adding the `<Designer />` and `<DesignerCanvas />` components to our page.

Place the `<Designer />` and `<DesignerCanvas />` components in the `src/App.tsx` file.

This will setup the designer and an infinite canvas with zoom, pan and controls.

```tsx
import { Designer, DesignerCanvas } from "@shadcn/designer"

export default function App() {
  return (
    <Designer layers={[]}>
      <DesignerCanvas />
    </Designer>
  )
}
```

Every design starts with a frame. The frame is the container that holds your layers.

A frame has a width and height. Use a `<DesignerFrame>` component to set the size of your design. This can be any size you want eg. 1024x1024, 1920x1080, 2048x2048, etc.

Place the `<DesignerFrame>` component inside the `<DesignerCanvas>` component.

```tsx
import {
  Designer,
  DesignerCanvas,
  DesignerFrame,
} from "@shadcn/designer"

export default function App() {
  return (
    <Designer layers={[]}>
      <DesignerCanvas>
        <DesignerFrame width={1024} height={1024} />
      </DesignerCanvas>
    </Designer>
  )
}
```

![Designer with frame](/images/02.png)

### Add a Layer

Now that we have the basic setup, let's add a layer.

```tsx
import {
  Designer,
  DesignerCanvas,
  DesignerFrame,
  type DesignerLayer,
} from "@shadcn/designer"

const layers: DesignerLayer[] = [
  {
    id: "1",
    name: "Heading",
    type: "text",
    value: "Hello World",
    style: {
      fontSize: "124px",
      fontWeight: "bold",
      width: "900px",
      height: "200px",
    },
  },
]

export default function App() {
  return (
    <Designer layers={layers}>
      <DesignerCanvas>
        <DesignerFrame width={1024} height={1024} />
      </DesignerCanvas>
    </Designer>
  )
}
```

This will add a `text` layer to the frame. You can click to drag the layer and use the resizer to change its width and height.

### Add an Action

An action is a component that can be used to display and transform the style of a layer. For example, you can use an action to change the width and height of a layer or the font size.

**@shadcn/designer** comes with a set of actions that you can use out of the box.

Let's add an action to change the dimension of a layer. We'll place it in a `<DesignerPanel>` component on the right side of the screen.

```tsx
import {
  Designer,
  DesignerCanvas,
  DesignerFrame,
  type DesignerLayer,
  ActionDimension,
  DesignerPanel,
  DesignerPane,
  DesignerPaneTitle,
  DesignerPaneContent,
} from "@shadcn/designer"

const layers: DesignerLayer[] = [
  {
    id: "1",
    name: "Heading",
    type: "text",
    value: "Hello World",
    style: {
      fontSize: "124px",
      fontWeight: "bold",
      width: "900px",
      height: "200px",
    },
  },
]

export default function App() {
  return (
    <Designer layers={layers}>
      <DesignerCanvas>
        <DesignerFrame width={1024} height={1024} />
      </DesignerCanvas>
      <DesignerPanel>
        <DesignerPane showForLayerTypes="all">
          <DesignerPaneTitle>Layer</DesignerPaneTitle>
          <DesignerPaneContent>
            <ActionDimension />
          </DesignerPaneContent>
        </DesignerPane>
      </DesignerPanel>
    </Designer>
  )
}
```

That was easy, right? Let's add more actions.

We'll add the `ActionPosition` and `ActionFill` actions to the panel.

```tsx
import {
  Designer,
  DesignerCanvas,
  DesignerFrame,
  type DesignerLayer,
  ActionDimension,
  ActionPosition,
  ActionFill,
  DesignerPanel,
  DesignerPane,
  DesignerPaneTitle,
  DesignerPaneContent,
} from "@shadcn/designer"

const layers: DesignerLayer[] = [
  {
    id: "1",
    name: "Heading",
    type: "text",
    value: "Hello World",
    style: {
      fontSize: "124px",
      fontWeight: "bold",
      width: "900px",
      height: "200px",
    },
  },
]

export default function App() {
  return (
    <Designer layers={layers}>
      <DesignerCanvas>
        <DesignerFrame width={1024} height={1024} />
      </DesignerCanvas>
      <DesignerPanel>
        <DesignerPane showForLayerTypes="all">
          <DesignerPaneTitle>Layer</DesignerPaneTitle>
          <DesignerPaneContent>
            <ActionDimension />
            <ActionPosition />
            <ActionFill />
          </DesignerPaneContent>
        </DesignerPane>
      </DesignerPanel>
    </Designer>
  )
}
```

Actions leverage the composable nature of the designer. This means you can add or remove actions and add multiple panels and panes to build custom designers.

### Add an Image Layer with Actions

Now, let's add an image layer to our frame. We'll also add an action to apply filters to the image.

```tsx
import {
  Designer,
  DesignerCanvas,
  DesignerFrame,
  type DesignerLayer,
  ActionDimension,
  ActionPosition,
  ActionFill,
  ActionImageFit,
  ActionImageFilter,
  DesignerPanel,
  DesignerPane,
  DesignerPaneTitle,
  DesignerPaneContent,
} from "@shadcn/designer"

const layers = [
  {
    id: "1",
    name: "Heading",
    type: "text",
    value: "Hello World",
    style: {
      fontSize: "124px",
      fontWeight: "bold",
      width: "900px",
      height: "200px",
    },
  },
  {
    id: "2",
    name: "Image",
    type: "image",
    value:
      "https://images.unsplash.com/photo-1474966862828-c58886978c8c?q=50&w=3284&auto=format",
    style: {
      width: "500px",
      height: "500px",
    },
  },
] satisfies DesignerLayer[]

export default function App() {
  return (
    <Designer layers={layers}>
      <DesignerCanvas>
        <DesignerFrame width={1000} height={1000} />
      </DesignerCanvas>
      <DesignerPanel>
        <DesignerPane showForLayerTypes="all">
          <DesignerPaneTitle>Text</DesignerPaneTitle>
          <DesignerPaneContent>
            <ActionDimension />
            <ActionPosition />
            <ActionFill />
          </DesignerPaneContent>
        </DesignerPane>
        <DesignerPane showForLayerTypes={["image"]}>
          <DesignerPaneTitle>Image</DesignerPaneTitle>
          <DesignerPaneContent>
            <ActionImageFit />
            <ActionImageFilter />
          </DesignerPaneContent>
        </DesignerPane>
      </DesignerPanel>
    </Designer>
  )
}
```

### Custom Actions

We also provide the necessary hooks and UI primitives to create your own actions.

Let's create an action to change the text alignment of a layer. We'll call it `ActionTextAlign`.

Place the following code in a `components/action-text-align.tsx` file.

```tsx
import { useDesignerAction } from "@shadcn/designer"
import {
  Label,
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@shadcn/designer/components"

export function ActionTextAlign() {
  const { value, updateLayers } = useDesignerAction({
    property: "textAlign",
    deserialize: (value) => value ?? "left",
    serialize: (value) => value ?? "left",
  })
  return (
    <div className="flex items-center justify-between">
      <Label htmlFor="text-align">Text align</Label>
      <Select value={value} onValueChange={(value) => updateLayers(value)}>
        <SelectTrigger id="text-align">
          <SelectValue placeholder="Select text align" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="left">Left</SelectItem>
          <SelectItem value="center">Center</SelectItem>
          <SelectItem value="right">Right</SelectItem>
        </SelectContent>
      </Select>
    </div>
  )
}
```

The code above uses the `useDesignerAction` hook to create an action. This hook provides the necessary state and methods to read (deserialize) and update (serialize) the layer style.

Add the `ActionTextAlign` component to the designer.

```tsx
import { ActionTextAlign } from "@/components/action-text-align"

export default function App() {
  return (
    <Designer layers={layers}>
      <DesignerCanvas>
        <DesignerFrame width={1000} height={1000} />
      </DesignerCanvas>
      <DesignerPanel>
        <DesignerPane showForLayerTypes="all">
          <DesignerPaneTitle>Text</DesignerPaneTitle>
          <DesignerPaneContent>
            <ActionDimension />
            <ActionPosition />
            <ActionFill />
            <ActionTextAlign />
          </DesignerPaneContent>
        </DesignerPane>
        <DesignerPane showForLayerTypes={["image"]}>
          <DesignerPaneTitle>Image</DesignerPaneTitle>
          <DesignerPaneContent>
            <ActionImageFit />
            <ActionImageFilter />
          </DesignerPaneContent>
        </DesignerPane>
      </DesignerPanel>
    </Designer>
  )
}
```

### What's Next?

That's it for now. In the next update, we'll ship designer presets where you can import a full-fledged designer with a predefined set of actions.

We're also working on more documentation and examples.

We'd love for you to try it out and give us feedback.