---
description: Fully customize appearance of components in diagrams.
---

# Custom Component Shapes

### Usage

Shape registration in the Appmixer SDK:

```
var appmixer = new Appmixer({
    componentShapes: {
        action: myCustomComponentShape,
        trigger: myCustomComponentShape,
        myUniqueShape: myCustomComponentShape
    },
});
```

Use "action" and "trigger" keys to override defaults. You can define a custom shape for any component in the system, shape reference in a component manifest looks like this:

```
{
    shape: "myUniqueShape"
}
```

Built-in shapes are `action`, `trigger`, `actionVertical` and `triggerVertical`.

{% content-ref url="../component-definition/manifest" %}
[manifest](../component-definition/manifest)
{% endcontent-ref %}

### Definition

| Key                      | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `options.updateCallback` | An optional method called before the component is updated. Accepts `element` argument.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `attributes`             | Element attributes, see:  [jointjs.dia.Cell.define](https://resources.jointjs.com/docs/jointjs/v3.2/joint.html#dia.Cell.define)                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `states`                 | <p>Definition for particular states of the component. The object structure is the same as the current scope <em>(except for the "states" entry)</em>.</p><p></p><p><code>@active</code><em>- the component is being modified</em></p><p><code>@invalid</code><em>- the component configuration is invalid</em></p><p><code>@referenced</code><em>- the component is highlighted</em></p><p><code>@unchecked</code><em>- the</em> component is selected</p><p><code>@checked</code> <em>- the component is deselected</em></p><p><code>@running</code> <em>- the flow is in a running state</em></p> |
| `ports.attributes`       | [P](https://resources.jointjs.com/docs/jointjs/v3.2/joint.html#dia.Element.ports.interface)orts attributes of [joint.dia.Element.ports.interface](https://resources.jointjs.com/docs/jointjs/v3.2/joint.html#dia.Element.ports.interface)                                                                                                                                                                                                                                                                                                                                                           |
| `ports.states`           | <p>Definition for particular states of individual ports.</p><p>The object structure is the same as the current scope <em>(except for the "states" entry)</em>.</p><p></p><p><code>@connected</code> <em>- the port is connected</em> </p>                                                                                                                                                                                                                                                                                                                                                           |
| `link.attributes`        | Link attributes, see: [jointjs.dia.Link](https://resources.jointjs.com/docs/jointjs/v3.2/joint.html#dia.Link)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `link.tools`             | Link attributes, see: [jointjs.linkTools](https://resources.jointjs.com/docs/jointjs/v3.5/joint.html#linkTools)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |

#### Special selectors

Dynamic properties of the component, such as label and icon, are mapped to optional selectors in the markup.

| Selector                    | tagName | Description                         |
| --------------------------- | ------- | ----------------------------------- |
| label                       | text    | Contains a label of the component.  |
| icon                        | image   | Contains an icon of the component.  |
| element-halo-copy-tooltip   | title   | "Copy" button tooltip.              |
| element-halo-cut-tooltip    | title   | "Cut" button tooltip.               |
| element-halo-remove-tooltip | title   | "Remove" button tooltip.            |

#### Special attributes

| Key     | Value      | Description                        |
| ------- | ---------- | ---------------------------------- |
| `event` | `"remove"` | The entry acts as a remove button. |

```
var customComponentShape = {
    attributes: {
        size: {
            height: 72,
            width: 72
        },
        markup: [
            {
                tagName: 'rect',
                selector: 'body'
            },
            {
                tagName: 'text',
                selector: 'label'
            },
            {
                tagName: 'image',
                selector: 'icon'
            },
            {
                tagName: 'g',
                selector: 'element-halo',
                children: [
                    {
                        tagName: 'image',
                        selector: 'element-halo-copy',
                        children: [
                            {
                                tagName: 'title',
                                selector: 'element-halo-copy-tooltip'
                            }
                        ]
                    },
                    {
                        tagName: 'image',
                        selector: 'element-halo-cut',
                        children: [
                            {
                                tagName: 'title',
                                selector: 'element-halo-cut-tooltip'
                            }
                        ]
                    },
                    {
                        tagName: 'image',
                        selector: 'element-halo-remove',
                        children: [
                            {
                                tagName: 'title',
                                selector: 'element-halo-remove-tooltip'
                            }
                        ]
                    }
                ]
            }
        ],
        attrs: {
            body: {
                fill: 'white',
                stroke: 'black',
                strokeWidth: 1,
                refWidth: 1,
                refHeight: 1
            },
            icon: {
                ref: 'body',
                refX: 0.5,
                refY: 0.5,
                xAlignment: 'middle',
                yAlignment: 'middle',
                width: 24,
                height: 24,
                clipPath: 'url(#icon-clip)'
            },
            label: {
                ref: 'body',
                textAnchor: 'middle',
                refX: 0.5,
                refY: '100%',
                refY2: 12,
                xAlignment: 'middle',
                fontFamily: 'sans-serif',
                fontWeight: 'bold',
                fontSize: 14,
                fill: 'black'
            },
            'element-halo': {
                display: 'none',
                ref: 'body',
                width: 68,
                height: 44,
                refX: 0.5,
                refY: 0,
                refY2: -24,
                xAlignment: 'middle'
            },
            'element-halo-copy': {
                event: 'element-copy',
                x: 0,
                y: 0,
                width: 20,
                height: 20,
                xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii01IC0yIDI0IDI0IiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIiBjbGFzcz0iaWNvbl9faWNvbiI+PHBhdGggZD0iTTUgMnYyaDRWMkg1em02IDBoMWEyIDIgMCAwIDEgMiAydjE0YTIgMiAwIDAgMS0yIDJIMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmgxYTIgMiAwIDAgMSAyLTJoNGEyIDIgMCAwIDEgMiAyem0wIDJhMiAyIDAgMCAxLTIgMkg1YTIgMiAwIDAgMS0yLTJIMnYxNGgxMFY0aC0xek00IDhoNmExIDEgMCAwIDEgMCAySDRhMSAxIDAgMSAxIDAtMnptMCA1aDZhMSAxIDAgMCAxIDAgMkg0YTEgMSAwIDAgMSAwLTJ6Ij48L3BhdGg+PC9zdmc+',
                cursor: 'pointer'
            },
            'element-halo-cut': {
                event: 'element-cut',
                x: 24,
                y: 0,
                width: 20,
                height: 20,
                cursor: 'pointer',
                xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0yLjUgLTIuNSAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiIgY2xhc3M9Imljb25fX2ljb24iPjxwYXRoIGQ9Ik05LjEwNyAxMC41NTRMNy4zNjMgMTIuNjNhMy45OTMgMy45OTMgMCAwIDEtLjE5MiA0Ljg5IDQuMDA0IDQuMDA0IDAgMCAxLTUuNjM1LjQ5MiAzLjk5MSAzLjk5MSAwIDAgMS0uNDkzLTUuNjI4IDQuMDA1IDQuMDA1IDAgMCAxIDQuNzg4LTEuMDM4TDcuODAxIDkgMi40MTMgMi41ODdhLjk5OC45OTggMCAwIDEgLjEyMy0xLjQwNyAxIDEgMCAwIDEgMS40MDkuMTIzbDUuMTYyIDYuMTQ0IDUuMTYxLTYuMTQ0YTEgMSAwIDAgMSAxLjQxLS4xMjMuOTk4Ljk5OCAwIDAgMSAuMTIzIDEuNDA3TDEwLjQxMiA5bDEuOTcgMi4zNDVhNC4wMDUgNC4wMDUgMCAwIDEgNC43ODggMS4wMzggMy45OTEgMy45OTEgMCAwIDEtLjQ5MyA1LjYyOCA0LjAwNCA0LjAwNCAwIDAgMS01LjYzNS0uNDkyIDMuOTkzIDMuOTkzIDAgMCAxLS4xOTItNC44OWwtMS43NDMtMi4wNzV6bS02LjI4NSA1LjkyN2EyIDIgMCAwIDAgMi41NzEtMy4wNiAyLjAwMiAyLjAwMiAwIDAgMC0yLjgxOC4yNDZjLS43MS44NDUtLjYgMi4xMDUuMjQ3IDIuODE0em0xMi41NyAwYTEuOTk2IDEuOTk2IDAgMCAwIC4yNDYtMi44MTQgMi4wMDIgMi4wMDIgMCAwIDAtMi44MTctLjI0NiAxLjk5NiAxLjk5NiAwIDAgMC0uMjQ3IDIuODE0Yy43MS44NDUgMS45NzIuOTU1IDIuODE4LjI0NnoiPjwvcGF0aD48L3N2Zz4'
            },
            'element-halo-remove': {
                event: 'element-remove',
                x: 48,
                y: 0,
                width: 20,
                height: 20,
                cursor: 'pointer',
                xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0zIC0yIDI0IDI0IiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIiBjbGFzcz0iaWNvbl9faWNvbiI+PHBhdGggZD0iTTYgMlYxYTEgMSAwIDAgMSAxLTFoNGExIDEgMCAwIDEgMSAxdjFoNGEyIDIgMCAwIDEgMiAydjFhMiAyIDAgMCAxLTIgMmgtLjEzM2wtLjY4IDEwLjJhMyAzIDAgMCAxLTIuOTkzIDIuOEg1LjgyNmEzIDMgMCAwIDEtMi45OTMtMi43OTZMMi4xMzcgN0gyYTIgMiAwIDAgMS0yLTJWNGEyIDIgMCAwIDEgMi0yaDR6bTEwIDJIMnYxaDE0VjR6TTQuMTQxIDdsLjY4NyAxMC4wNjhhMSAxIDAgMCAwIC45OTguOTMyaDYuMzY4YTEgMSAwIDAgMCAuOTk4LS45MzRMMTMuODYyIDdoLTkuNzJ6TTcgOGExIDEgMCAwIDEgMSAxdjdhMSAxIDAgMCAxLTIgMFY5YTEgMSAwIDAgMSAxLTF6bTQgMGExIDEgMCAwIDEgMSAxdjdhMSAxIDAgMCAxLTIgMFY5YTEgMSAwIDAgMSAxLTF6Ij48L3BhdGg+PC9zdmc+'
            },
            'element-halo-copy-tooltip': {
                fontFamily: 'nunitosans-regular, Helvetica, Arial, sans-serif',
                fontSize: 13
            },
            'element-halo-cut-tooltip': {
                fontFamily: 'nunitosans-regular, Helvetica, Arial, sans-serif',
                fontSize: 13
            },
            'element-halo-remove-tooltip': {
                fontFamily: 'nunitosans-regular, Helvetica, Arial, sans-serif',
                fontSize: 13
            }
        }
    },
    ports: {
        attributes: {
            in: {
                position: {
                    name: 'left'
                },
                attrs: {
                    '.port-body': {
                        r: 5,
                        strokeWidth: 2,
                        stroke: 'black',
                        fill: 'white'
                    },
                    'connection-port-label': {
                        fill: 'black',
                        fontFamily: 'sans-serif',
                        fontSize: 12
                    }
                },
                label: {
                    position: {
                        name: 'left',
                        args: {
                            x: -8,
                            y: 14
                        }
                    },
                    markup: [
                        {
                            tagName: 'text',
                            selector: 'connection-port-label'
                        }
                    ]
                }
            },
            out: {
                position: {
                    name: 'right'
                },
                attrs: {
                    '.port-body': {
                        r: 5,
                        strokeWidth: 2,
                        stroke: 'black',
                        fill: 'white'
                    },
                    'connection-port-label': {
                        fill: 'black',
                        fontFamily: 'sans-serif',
                        fontSize: 12
                    }
                },
                label: {
                    position: {
                        name: 'right',
                        args: {
                            x: 8,
                            y: 14
                        }
                    },
                    markup: [
                        {
                            tagName: 'text',
                            selector: 'connection-port-label'
                        }
                    ]
                }
            }
        }
    },
    link: {
        attributes: {
            router: {
                name: 'metro'
            },
            connector: {
                name: 'rounded',
                args: {
                    radius: 8
                }
            },
            markup: [
                {
                    tagName: 'path',
                    selector: 'wrapper'
                },
                {
                    tagName: 'path',
                    selector: 'line'
                }
            ],
            tools: [
                {
                    z: 100,
                    distance: '50%',
                    event: 'link-remove',
                    markup: [
                        {
                            tagName: 'circle',
                            selector: 'button',
                            attributes: {
                                cx: -0.5,
                                cy: -0.5,
                                r: 10,
                                fill: config.colors.bodyStrokeActive,
                                cursor: 'pointer'
                            }
                        },
                        {
                            tagName: 'path',
                            selector: 'icon',
                            attributes: {
                                pointerEvents: 'none',
                                transform: 'translate(-6.4 -7)',
                                d: 'M8,1.333h3.333A.667.667,0,0,1,12,2V3.333A.667.667,0,0,1,11.333,4H.667A.667.667,0,0,1,0,3.333V2a.667.667,0,0,1,.667-.667H4V.667A.667.667,0,0,1,4.667,0H7.333A.667.667,0,0,1,8,.667Zm2.533,4-.409,6.133a2,2,0,0,1-2,1.867H3.884a2,2,0,0,1-2-1.864L1.47,5.333ZM4.667,6A.667.667,0,0,0,4,6.667v4.667a.667.667,0,0,0,1.333,0V6.667A.667.667,0,0,0,4.667,6ZM7.333,6a.667.667,0,0,0-.667.667v4.667a.667.667,0,0,0,1.333,0V6.667A.667.667,0,0,0,7.333,6Z',
                                fill: 'white'
                            }
                        }
                    ]
                }
            ],
            attrs: {
                line: {
                    pointerEvents: 'none',
                    connection: true,
                    stroke: 'black',
                    strokeWidth: 1.5,
                    strokeDasharray: '4 4',
                    fill: 'transparent'
                },
                wrapper: {
                    connection: true,
                    cursor: 'pointer',
                    stroke: 'transparent',
                    strokeWidth: 24,
                    fill: 'transparent'
                }
            }
        }
    },
    states: {
        '@active': {
            attributes: {
                attrs: {
                    body: {
                        stroke: 'blue',
                        strokeWidth: 3
                    },
                    label: {
                        fill: 'black'
                    },
                    'element-halo': {
                        display: 'initial'
                    }
                }
            },
            ports: {
                attributes: {
                    in: {
                        attrs: {
                            '.port-body': {
                                stroke: 'blue'
                            }
                        }
                    },
                    out: {
                        attrs: {
                            '.port-body': {
                                stroke: 'blue'
                            }
                        }
                    }
                }
            },
            link: {
                attributes: {
                    attrs: {
                        line: {
                            strokeDasharray: 0,
                            stroke: 'blue'
                        }
                    }
                }
            }
        },
        '@invalid': {
            attributes: {
                attrs: {
                    body: {
                        stroke: 'red',
                        strokeWidth: 2
                    },
                    label: {
                        fill: 'red'
                    }
                }
            },
            ports: {
                attributes: {
                    in: {
                        attrs: {
                            '.port-body': {
                                stroke: 'red'
                            }
                        }
                    },
                    out: {
                        attrs: {
                            '.port-body': {
                                stroke: 'red'
                            }
                        }
                    }
                }
            }
        }
    }
};

var customSelectionShape = {
    attributes: {
        markup: [
            {
                tagName: 'rect',
                selector: 'body'
            },
            {
                tagName: 'g',
                selector: 'halo',
                children: [
                    {
                        tagName: 'image',
                        selector: 'halo-copy'
                    },
                    {
                        tagName: 'image',
                        selector: 'halo-cut'
                    },
                    {
                        tagName: 'image',
                        selector: 'halo-remove'
                    }
                ]
            }
        ],
        attrs: {
            body: {
                pointerEvents: 'none',
                fill: 'rgba(0, 0, 0, 0.1)',
                stroke: 'black',
                strokeWidth: 1
            },
            halo: {
                ref: 'body',
                width: 68,
                height: 44,
                refX: 0.5,
                refY: 0,
                refY2: -24,
                xAlignment: 'middle'
            },
            'halo-copy': {
                event: 'element-copy',
                x: 0,
                y: 0,
                width: 20,
                height: 20,
                xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii01IC0yIDI0IDI0IiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIiBjbGFzcz0iaWNvbl9faWNvbiI+PHBhdGggZD0iTTUgMnYyaDRWMkg1em02IDBoMWEyIDIgMCAwIDEgMiAydjE0YTIgMiAwIDAgMS0yIDJIMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmgxYTIgMiAwIDAgMSAyLTJoNGEyIDIgMCAwIDEgMiAyem0wIDJhMiAyIDAgMCAxLTIgMkg1YTIgMiAwIDAgMS0yLTJIMnYxNGgxMFY0aC0xek00IDhoNmExIDEgMCAwIDEgMCAySDRhMSAxIDAgMSAxIDAtMnptMCA1aDZhMSAxIDAgMCAxIDAgMkg0YTEgMSAwIDAgMSAwLTJ6Ij48L3BhdGg+PC9zdmc+',
                cursor: 'pointer'
            },
            'halo-cut': {
                event: 'element-cut',
                x: 24,
                y: 0,
                width: 20,
                height: 20,
                cursor: 'pointer',
                xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0yLjUgLTIuNSAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiIgY2xhc3M9Imljb25fX2ljb24iPjxwYXRoIGQ9Ik05LjEwNyAxMC41NTRMNy4zNjMgMTIuNjNhMy45OTMgMy45OTMgMCAwIDEtLjE5MiA0Ljg5IDQuMDA0IDQuMDA0IDAgMCAxLTUuNjM1LjQ5MiAzLjk5MSAzLjk5MSAwIDAgMS0uNDkzLTUuNjI4IDQuMDA1IDQuMDA1IDAgMCAxIDQuNzg4LTEuMDM4TDcuODAxIDkgMi40MTMgMi41ODdhLjk5OC45OTggMCAwIDEgLjEyMy0xLjQwNyAxIDEgMCAwIDEgMS40MDkuMTIzbDUuMTYyIDYuMTQ0IDUuMTYxLTYuMTQ0YTEgMSAwIDAgMSAxLjQxLS4xMjMuOTk4Ljk5OCAwIDAgMSAuMTIzIDEuNDA3TDEwLjQxMiA5bDEuOTcgMi4zNDVhNC4wMDUgNC4wMDUgMCAwIDEgNC43ODggMS4wMzggMy45OTEgMy45OTEgMCAwIDEtLjQ5MyA1LjYyOCA0LjAwNCA0LjAwNCAwIDAgMS01LjYzNS0uNDkyIDMuOTkzIDMuOTkzIDAgMCAxLS4xOTItNC44OWwtMS43NDMtMi4wNzV6bS02LjI4NSA1LjkyN2EyIDIgMCAwIDAgMi41NzEtMy4wNiAyLjAwMiAyLjAwMiAwIDAgMC0yLjgxOC4yNDZjLS43MS44NDUtLjYgMi4xMDUuMjQ3IDIuODE0em0xMi41NyAwYTEuOTk2IDEuOTk2IDAgMCAwIC4yNDYtMi44MTQgMi4wMDIgMi4wMDIgMCAwIDAtMi44MTctLjI0NiAxLjk5NiAxLjk5NiAwIDAgMC0uMjQ3IDIuODE0Yy43MS44NDUgMS45NzIuOTU1IDIuODE4LjI0NnoiPjwvcGF0aD48L3N2Zz4'
            },
            'halo-remove': {
                event: 'element-remove',
                x: 48,
                y: 0,
                width: 20,
                height: 20,
                cursor: 'pointer',
                xlinkHref: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0zIC0yIDI0IDI0IiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIiBjbGFzcz0iaWNvbl9faWNvbiI+PHBhdGggZD0iTTYgMlYxYTEgMSAwIDAgMSAxLTFoNGExIDEgMCAwIDEgMSAxdjFoNGEyIDIgMCAwIDEgMiAydjFhMiAyIDAgMCAxLTIgMmgtLjEzM2wtLjY4IDEwLjJhMyAzIDAgMCAxLTIuOTkzIDIuOEg1LjgyNmEzIDMgMCAwIDEtMi45OTMtMi43OTZMMi4xMzcgN0gyYTIgMiAwIDAgMS0yLTJWNGEyIDIgMCAwIDEgMi0yaDR6bTEwIDJIMnYxaDE0VjR6TTQuMTQxIDdsLjY4NyAxMC4wNjhhMSAxIDAgMCAwIC45OTguOTMyaDYuMzY4YTEgMSAwIDAgMCAuOTk4LS45MzRMMTMuODYyIDdoLTkuNzJ6TTcgOGExIDEgMCAwIDEgMSAxdjdhMSAxIDAgMCAxLTIgMFY5YTEgMSAwIDAgMSAxLTF6bTQgMGExIDEgMCAwIDEgMSAxdjdhMSAxIDAgMCAxLTIgMFY5YTEgMSAwIDAgMSAxLTF6Ij48L3BhdGg+PC9zdmc+'
            }
        }
    }
};

new Appmixer({
    componentShapes: {
        action: customComponentShape,
        trigger: customComponentShape,
        selection: customSelectionShape        
    }
});
```

![](https://3844357853-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LATDgYqVMe0hChW7efU%2F-MGHzNxmKor0ubNi5M84%2F-MGJXPh22gMASTxm7Jpp%2FScreenshot%202020-09-03%20at%2022.07.51.png?alt=media\&token=d5700d21-4f9b-46c1-bd3b-a64d0cf46df1)
