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#
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 toCanvasMenu.Button
- Pre-built button implementation with action and labelCanvasMenu.Divider
- Visual separator between menu groupsCanvasMenu.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 whenisVisible(_:)
returnstrue
. -
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 elementitems
- 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’saction
. 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
- aView
that describes the purpose of the button’saction
. 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.
Button | ID | Description |
---|---|---|
CanvasMenu.Buttons.bringForward | CanvasMenu.Buttons.ID.bringForward | Brings forward currently selected design block via editor event .bringSelectionForward . |
CanvasMenu.Buttons.sendBackward | CanvasMenu.Buttons.ID.sendBackward | Sends backward currently selected design block via editor event .sendSelectionBackward . |
CanvasMenu.Buttons.duplicate | CanvasMenu.Buttons.ID.duplicate | Duplicates currently selected design block via editor event .duplicateSelection . |
CanvasMenu.Buttons.delete | CanvasMenu.Buttons.ID.delete | Deletes currently selected design block via editor event .deleteSelection . |
CanvasMenu.Buttons.selectGroup | CanvasMenu.Buttons.ID.selectGroup | Selects the group design block that contains the currently selected design block via editor event .selectGroupForSelection . |