Create a new icon type

To create a custom icon you need to implement the IIcon interface. This tells Blockly how you want your icon to be rendered, what you want it to do if it's clicked, etc.

We recommend subclassing the Icon abstract class, because it already provides default implementations of many methods in the IIcon interface.

class MyIcon extends Blockly.icons.Icon {
  // The constructor should always take in the source block so that svg elements
  // can be properly created.
  constructor(sourceBlock) {
    super(sourceBlock);
  }
}

Specify the icon's type

The getType method returns a value representing the type of the icon. It is used for registering the icon for serialization, and retrieving the icon from getIcon.

JavaScript

  getType() {
    return new Blockly.icons.IconType('my_icon');
  }

TypeScript

  getType(): Blockly.icons.IconType<MyIcon> {
    return new Blockly.icons.IconType<MyIcon>('my_icon');
  }

Create the icon's view

The icon's view refers to the SVG elements that live on the block.

Initialize the view

The initView method is where you create the SVG elements of your icon that live on the block. New elements should be children of the this.svgRoot element so that they get automatically cleaned up when the icon is destroyed.

The Blockly.utils.dom module provides a clean interface for instantiating SVGs.

initView(pointerdownListener) {
  if (this.svgRoot) return;  // Already initialized.

  // This adds the pointerdownListener to the svgRoot element.
  // If you do not call `super` you must do this yourself.
  super.initView(pointerdownListener);

  Blockly.utils.dom.createSvgElement(
    Blockly.utils.Svg.CIRCLE,
    {
      'class': 'my-css-class',
      'r': '8',
      'cx': '8',
      'cy': '8',
    },
    this.svgRoot  // Append to the svgRoot.
  );
}

Return the size

The getSize method returns the size of the icon, so that the renderer can make the block wide enough for it.

The size is in arbitrary "workspace units" (which don't change as the workspace changes scale).

getSize() {
  return new Blockly.utils.Size(16, 16);
}

Set the order

Icons have a static order within the block. For example, the built-in mutator icons are always shown in front of comment icons, which are shown in front of warning icons.

The order is controlled by the value returned by the getWeight method. Icons with more positive weights are rendered after icons with less positive weights.

getWeight() {
  return 10;
}

Implement onclick behavior

Many icons are interactive and do something when they are clicked. For example, the built-in icons all show a bubble when they are clicked. You can use the onClick method to implement this.

onClick() {
  // Do something when clicked.
}

Respond to block changes

Some icons also want to respond to changes in the block, in particular changes to editability and collapsed-ness.

Editability

If your icon should behave differently depending on whether the block is editable or not (for example, not being clickable when the block is not editable), implement the updateEditable method. This method gets called automatically when the block's editable status changes.

updateEditable() {
  if (this.sourceBlock.isEditable()) {
    // Do editable things.
  } else {
    // Do non-editable things.
  }
}

Collapsed-ness

Some icons are shown when the block is collapsed, but by default they are not. If you want your icon to be shown, override the isShownWhenCollapsed method to return true.

isShownWhenCollapsed() {
  return true;
}

And then override the updateCollapsed method.

updateCollapsed() {
  // By default icons are hidden when the block is collapsed. We want it to
  // be shown, so do nothing.
}

Dispose of the icon

Icons should clean up any dom elements or external references when they are disposed. By default, anything that is appended to this.svgRoot gets destroyed, but other references need to be manually cleaned up. This should be done within the dispose method.

dispose() {
  // Always call super!
  super.dispose();

  this.myBubble?.dispose();
  this.myOtherReference?.dispose();
}