diff --git a/src/html_toast.ts b/src/html_toast.ts
new file mode 100644
index 00000000..7e6c73c4
--- /dev/null
+++ b/src/html_toast.ts
@@ -0,0 +1,54 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import * as Blockly from 'blockly/core';
+
+/**
+ * Configuration options for toasts.
+ */
+interface HtmlToastOptions extends Blockly.ToastOptions {
+ element?: HTMLElement;
+}
+
+/**
+ * Custom toast implementation that supports HTML elements in toast messages.
+ *
+ * After registering, call
+ `Blockly.dialog.toast(workspace, {element: , message: });`
+ * to display an HTML-based toast.
+ */
+class HtmlToast extends Blockly.Toast {
+ /**
+ * Creates the body of the toast for display.
+ *
+ * @param workspace The workspace the toast will be displayed on.
+ * @param options Configuration options for toast appearance/behavior.
+ * @returns The body for the toast.
+ */
+ protected static override createDom(
+ workspace: Blockly.WorkspaceSvg,
+ options: HtmlToastOptions,
+ ) {
+ const dom = super.createDom(workspace, options);
+ const contents = dom.querySelector('div');
+ if (
+ contents &&
+ 'element' in options &&
+ options.element instanceof HTMLElement
+ ) {
+ contents.innerHTML = '';
+ contents.appendChild(options.element);
+ }
+ return dom;
+ }
+}
+
+/**
+ * Registers HtmlToast as the default toast implementation for Blockly.
+ */
+export function registerHtmlToast() {
+ Blockly.dialog.setToast(HtmlToast.show.bind(HtmlToast));
+}
diff --git a/src/index.ts b/src/index.ts
index efe96d58..32f55d73 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -7,6 +7,7 @@
import * as Blockly from 'blockly/core';
import {NavigationController} from './navigation_controller';
import {enableBlocksOnDrag} from './disabled_blocks';
+import {registerHtmlToast} from './html_toast';
/** Plugin for keyboard navigation. */
export class KeyboardNavigation {
@@ -82,6 +83,8 @@ export class KeyboardNavigation {
});
workspace.getSvgGroup().appendChild(this.workspaceFocusRing);
this.resizeWorkspaceRings();
+
+ registerHtmlToast();
}
private resizeWorkspaceRings() {
diff --git a/test/webdriverio/test/toast_test.ts b/test/webdriverio/test/toast_test.ts
new file mode 100644
index 00000000..cd4721d5
--- /dev/null
+++ b/test/webdriverio/test/toast_test.ts
@@ -0,0 +1,38 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import * as chai from 'chai';
+import * as Blockly from 'blockly/core';
+import {PAUSE_TIME, testFileLocations, testSetup} from './test_setup.js';
+
+suite('HTML toasts', function () {
+ setup(async function () {
+ this.browser = await testSetup(testFileLocations.BASE);
+ await this.browser.pause(PAUSE_TIME);
+ });
+
+ test('Can be displayed', async function () {
+ const equal = await this.browser.execute(() => {
+ const element = document.createElement('div');
+ element.id = 'testToast';
+ element.innerHTML = 'This is a test';
+
+ const options = {
+ element,
+ message: 'Placeholder',
+ };
+ Blockly.dialog.toast(
+ Blockly.getMainWorkspace() as Blockly.WorkspaceSvg,
+ options,
+ );
+
+ // Ensure that the element displayed in the toast is the one we specified.
+ return document.querySelector('.blocklyToast #testToast') === element;
+ });
+
+ chai.assert.isTrue(equal);
+ });
+});