Search
Loading...
Skip to content

Canvas Menu

The canvas menu provides immediate access to frequently-used actions when you select a design element, offering operations like duplicate, delete, layer reordering, and grouping controls directly on the canvas. This guide shows you how to customize these contextual actions to streamline your users’ editing workflow and match your app’s feature priorities. While examples use the Design Editor, the same configuration principles apply to all editor solutions.

Explore a complete code sample on GitHub.

Canvas Menu Architecture#

Canvas Menu

The canvas menu displays horizontally when a design element is selected, offering quick access to editing actions relevant to that element type.

Key Components:

  • CanvasMenu.Item - Protocol that all canvas menu items conform to
  • CanvasMenu.Button - Pre-built button implementation with action and label
  • CanvasMenu.Divider - Visual separator between menu groups
  • CanvasMenu.Context - Provides access to the engine, asset library, and selected element
  • Custom Items - Create fully custom components by implementing CanvasMenu.Item

Configuration#

Canvas menu customization uses SwiftUI modifiers in the .imgly namespace. You can configure the complete item list or modify the default items.

Available modifiers:

  • canvasMenuItems - Define the complete list of canvas menu items and their order. Items are only displayed when isVisible(_:) returns true.

  • modifyCanvasMenuItems - Modify the default item list by adding, replacing, or removing specific items without rebuilding the entire configuration.

The CanvasMenu.Context provides access to the engine, asset library, event handler, and currently selected element. Use the provided selection for logic instead of querying the engine directly, as it’s optimized for UI presentation timing.

Default Canvas Menu Items#

The default configuration includes essential editing actions with logical grouping:

.imgly.canvasMenuItems { context in
CanvasMenu.Buttons.selectGroup()
CanvasMenu.Divider()
CanvasMenu.Buttons.bringForward()
CanvasMenu.Buttons.sendBackward()
CanvasMenu.Divider()
CanvasMenu.Buttons.duplicate()
CanvasMenu.Buttons.delete()
}

Modify Canvas Menu Items#

Use the .imgly.modifyCanvasMenuItems modifier to adjust the default item list without rebuilding the entire configuration:

.imgly.modifyCanvasMenuItems { context, items in

Parameters:

  • context - provides access to the engine, asset library, and selected element
  • items - mutable array of canvas menu items that can be modified

Available modification operations:

  • addFirst - prepends new items at the beginning:
items.addFirst {
CanvasMenu.Button(id: "my.package.canvasMenu.button.first") { context in
print("First Button action")
} label: { context in
Label("First Button", systemImage: "arrow.backward.circle")
}
}
  • addLast - appends new items at the end:
items.addLast {
CanvasMenu.Button(id: "my.package.canvasMenu.button.last") { context in
print("Last Button action")
} label: { context in
Label("Last Button", systemImage: "arrow.forward.circle")
}
}
  • addAfter - adds new items right after a specific item:
items.addAfter(id: CanvasMenu.Buttons.ID.bringForward) {
CanvasMenu.Button(id: "my.package.canvasMenu.button.afterBringForward") { context in
print("After Bring Forward action")
} label: { context in
Label("After Bring Forward", systemImage: "arrow.forward.square")
}
}
  • addBefore - adds new items right before a specific item:
items.addBefore(id: CanvasMenu.Buttons.ID.sendBackward) {
CanvasMenu.Button(id: "my.package.canvasMenu.button.beforeSendBackward") { context in
print("Before Send Backward action")
} label: { context in
Label("Before Send Backward", systemImage: "arrow.backward.square")
}
}
  • replace - replaces an existing item with new items:
items.replace(id: CanvasMenu.Buttons.ID.duplicate) {
CanvasMenu.Button(id: "my.package.canvasMenu.button.replacedDuplicate") { context in
print("Replaced Duplicate action")
} label: { context in
Label("Replaced Duplicate", systemImage: "arrow.uturn.down.square")
}
}
  • remove - removes an existing item:
items.remove(id: CanvasMenu.Buttons.ID.delete)

CanvasMenu.Item Configuration#

Each CanvasMenu.Item requires a unique id for SwiftUI’s ForEach rendering. You have multiple options for creating canvas menu items, from simple predefined buttons to fully custom implementations.

Use Predefined Buttons#

Start with predefined buttons from the CanvasMenu.Buttons namespace. All available predefined buttons are listed below.

CanvasMenu.Buttons.duplicate()

Customize Predefined Buttons#

Customize any predefined button by overriding its default parameters:

CanvasMenu.Buttons.delete(
action: { context in
context.eventHandler.send(.deleteSelection)
},
label: { context in
Label { Text("Delete") } icon: { Image.imgly.delete }
},
isEnabled: { context in true },
isVisible: { context in
try context.engine.block.isAllowedByScope(context.selection.block, key: "lifecycle/destroy")
},
)

Available parameters:

  • action - the action to perform when the user triggers the button. Deletes the selected element in this example.

  • label - the view that describes the purpose of the button’s action. Don’t encode visibility logic in this view.

  • isEnabled - whether the button is enabled. Use context to determine state.

  • isVisible - whether the button should be visible. This example shows visibility logic based on element scope permissions.

Create New Buttons#

Create custom buttons when predefined options don’t meet your needs:

CanvasMenu.Button(
id: "my.package.canvasMenu.button.newButton",
) { context in
print("New Button action")
} label: { context in
Label("New Button", systemImage: "star.circle")
} isEnabled: { context in
true
} isVisible: { context in
true
}

Required and optional parameters:

  • id - the unique id of the button. This parameter is required.

  • action - the action to perform when the user triggers the button. This parameter is required.

  • label - a View that describes the purpose of the button’s action. Don’t encode visibility logic in this view. This parameter is required.

  • isEnabled - whether the button is enabled. By default, true is always used.

  • isVisible - whether the button should be visible. Prefer using this parameter for visibility logic. By default, true is always used.

Create New Custom Items#

For completely custom implementations, create a type conforming to the CanvasMenu.Item protocol:

private struct CustomCanvasMenuItem: CanvasMenu.Item {
var id: EditorComponentID { "my.package.canvasMenu.newCustomItem" }
func body(_ context: CanvasMenu.Context) throws -> some View {
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(.conicGradient(colors: [.red, .yellow, .green, .cyan, .blue, .purple, .red], center: .center))
Text("New Custom Item")
.padding(4)
}
.onTapGesture {
print("New Custom Item action")
}
}
func isVisible(_ context: CanvasMenu.Context) throws -> Bool {
true
}
}

Then use it in your canvas menu items:

CustomCanvasMenuItem()

Protocol requirements:

  • var id: EditorComponentID { get } - the unique id of the item. This property is required.

  • func body(_: CanvasMenu.Context) throws -> some View - the body of your view. Don’t encode visibility logic in this view. This property is required.

  • func isVisible(_: CanvasMenu.Context) throws -> Bool - whether the item should be visible. Prefer using this parameter for visibility logic. By default, true is always used.

List of Available CanvasMenu.Buttons#

All predefined buttons are available as static functions in the CanvasMenu.Buttons namespace. Each function returns a CanvasMenu.Button with default parameters that you can customize as shown in the Customize Predefined Buttons section.

ButtonIDDescription
CanvasMenu.Buttons.bringForwardCanvasMenu.Buttons.ID.bringForwardBrings forward currently selected design block via editor event .bringSelectionForward.
CanvasMenu.Buttons.sendBackwardCanvasMenu.Buttons.ID.sendBackwardSends backward currently selected design block via editor event .sendSelectionBackward.
CanvasMenu.Buttons.duplicateCanvasMenu.Buttons.ID.duplicateDuplicates currently selected design block via editor event .duplicateSelection.
CanvasMenu.Buttons.deleteCanvasMenu.Buttons.ID.deleteDeletes currently selected design block via editor event .deleteSelection.
CanvasMenu.Buttons.selectGroupCanvasMenu.Buttons.ID.selectGroupSelects the group design block that contains the currently selected design block via editor event .selectGroupForSelection.