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.

Layer Type

Start by defining the layer type.

import { type LayerType } from "@shadcn/designer"
 
// Logo data.
const LOGOS = [
  {
    name: "Default",
		path: `<svg .../>`,
  },
  {
    name: "Anthropic",
		path: `<svg .../>`,
  },
	// ...
]
 
// 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 (
        <div
          dangerouslySetInnerHTML={{ __html: logo?.path }}
          style={{
            ...layer.contentStyle,
            width: "100%",
            height: "100%",
          }}
        />
      )
    },
  },
] satisfies LayerType[]

Designer

Create the designer with a default layer. This will render a frame with a logo layer with the default logo.

components/custom-designer.tsx
function CustomDesigner() {
  return (
    <Designer
      layerTypes={customLayerTypes}
      defaultLayers={[
        {
          id: "logo-1",
          name: "Logo",
          type: "logo",
          value: "Default",
          cssVars: {
            "--width": "400px",
            "--height": "400px",
            "--translate-x": "312px",
            "--translate-y": "312px",
          },
        },
      ]}
    >
      <DesignerContent>
        <DesignerCanvas>
          <DesignerFrame />
        </DesignerCanvas>
      </DesignerContent>
    </Designer>
  )
}

ActionLogoPicker

To add a logo picker action, we can use the <Action component and the useSetLayersProperty hook to update the value property of the logo layer.

function ActionLogoPicker() {
  const selectedLayers = useSelectedLayers()
  const layerIds = useSelectedLayerIds()
  const setLayersProperty = useSetLayersProperty()
 
  return (
    <Action className="gap-0">
      <ActionLabel className="sr-only" htmlFor="logo-picker">
        Select Logo
      </ActionLabel>
      <ActionControls>
        <Select
          disabled={!layerIds.length}
          value={selectedLayers[0]?.value ?? ""}
          onValueChange={(value) => {
            setLayersProperty(layerIds, "value", value)
          }}
        >
          <SelectTrigger id="logo-picker" className="w-28">
            <SelectValue placeholder="Select Logo" />
          </SelectTrigger>
          <SelectContent>
            {LOGOS.map((logo) => (
              <SelectItem key={logo.name} value={logo.name}>
                <span dangerouslySetInnerHTML={{ __html: logo.path }} />
                {logo.name}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </ActionControls>
    </Action>
  )
}

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.

 
const DEFAULT_FILL = "#000000"
const fillAction = createLayerCssVarAction("--fill", DEFAULT_FILL)
 
function ActionLogoFill() {
  const selectedLayers = useSelectedLayers()
  const [fill, setFill] = useLayerCssVarAction(fillAction)
 
  return (
    <Action>
      <ActionLabel className="sr-only" htmlFor="logo-fill">
        Fill
      </ActionLabel>
      <ActionControls>
        <Popover>
          <PopoverTrigger asChild>
            <Button
              className="flex-1 uppercase data-[empty=true]:text-muted-foreground data-[empty=true]:capitalize "
              data-empty={fill === ""}
              disabled={!selectedLayers.length}
            >
              <span
                className="ml-auto flex size-3 shrink-0 rounded-xs"
                style={{ backgroundColor: fill === "" ? DEFAULT_FILL : fill }}
              />
            </Button>
          </PopoverTrigger>
          <PopoverContent side="bottom" className="w-48 p-2" sideOffset={12}>
            <ColorPicker value={fill} onValueChange={setFill} />
          </PopoverContent>
        </Popover>
      </ActionControls>
    </Action>
  )
}