UI API
Extend the app’s UI through dmn.ui.
UI API is only available in main window. Calling from overlay shows a warning and does nothing.
Context Menu
Key Menu (addKeyMenuItem)
Add items to the menu shown when right-clicking a key.
const menuId = dmn.ui.contextMenu.addKeyMenuItem({
id: 'copy-keycode',
label: 'Copy Key Code',
onClick: (context) => {
navigator.clipboard.writeText(context.keyCode);
},
});Context Object:
| Property | Type | Description |
|---|---|---|
keyCode | string | Canonical slot identifier (e.g. “D”, “LEFT CTRL+Z”, “F|NUMPAD 4”) |
id | string | Stable element ID (UUID). Keeps identifying the same key across reorders and mode switches |
index | number | Key index. Deprecated: only valid for the current snapshot, use id for identity |
position | KeyPosition | Key position info |
mode | string | Current key mode |
Grid Menu (addGridMenuItem)
Add items to the menu shown when right-clicking empty grid space.
So the menu stays short no matter how many plugins are installed, items added here are grouped under a single Plugins submenu.
They keep their registration order and position does not apply.
dmn.ui.contextMenu.addGridMenuItem({
id: 'add-timer',
label: 'Add Timer',
onClick: (context) => {
createTimer(context.position);
},
});Context Object:
| Property | Type | Description |
|---|---|---|
position | { dx, dy } | Click position (grid coordinates) |
mode | string | Current key mode |
Menu Item Options
{
id: "my-menu", // Unique ID within plugin
label: "Menu Item", // Display text
position: "bottom", // "top" | "bottom" (default: bottom, key menu only)
// Conditional visibility
visible: (context) => context.mode === "4key",
// Conditional disable
disabled: (context) => context.position.count === 0,
// Click handler
onClick: async (context) => {
// ...
},
}visible and disabled accept a boolean or a function of the context. A function that throws is treated as hidden / disabled (the rest of the menu still renders), and non-function values are coerced by truthiness. 0, "", and null hide an item when passed to visible, and leave it enabled when passed to disabled.
Menu Management
// Update menu
dmn.ui.contextMenu.updateMenuItem(menuId, {
label: 'New Label',
disabled: true,
});
// Remove specific menu
dmn.ui.contextMenu.removeMenuItem(menuId);
// Remove all menus from this plugin
dmn.ui.contextMenu.clearMyMenuItems();Display Element
displayElement is a low-level (primitive) API, not recommended for direct
use. For adding custom UI elements to grid and overlay, strongly recommend
using the declarative defineElement approach.
Add custom UI elements to grid and overlay.
Basic Usage
const panel = dmn.ui.displayElement.add({
html: `<div style="background: #000; color: #fff; padding: 16px;">
Hello!
</div>`,
position: { x: 100, y: 100 },
draggable: true,
});
// Remove
panel.remove();State-based Template
const panel = dmn.ui.displayElement.add({
position: { x: 100, y: 100 },
state: { value: 0, history: [] },
template: (state, { html }) => html`
<div style="background: #000; color: #fff; padding: 16px;">
<strong>${state.value}</strong>
<div style="display: flex; gap: 2px;">
${state.history.map(
(v) => html`
<span
style="width: 4px; height: ${v}px; background: #86EFAC;"
></span>
`,
)}
</div>
</div>
`,
});
// State update → auto re-render
panel.setState({ value: 42, history: [10, 20, 30] });Event Handlers
dmn.ui.displayElement.add({
html: `<div>Click me</div>`,
position: { x: 100, y: 100 },
// Direct function (recommended)
onClick: async () => {
console.log('Clicked!');
},
onPositionChange: async (pos) => {
await dmn.plugin.storage.set('position', pos);
},
onDelete: async () => {
console.log('Deleted!');
},
});Instance Methods
| Method | Description |
|---|---|
setState(updates) | Update state (re-renders template) |
setData(updates) | Update state (setState alias) |
getState() | Get current state |
setText(selector, text) | Set element text |
setHTML(selector, html) | Set element HTML |
setStyle(selector, styles) | Apply styles |
addClass(selector, ...classes) | Add classes |
removeClass(selector, ...classes) | Remove classes |
toggleClass(selector, className) | Toggle class |
query(selector) | Find element (including Shadow DOM) |
update(config) | Update metadata |
remove() | Remove element |
Anchor
Pins an element to a specific key:
dmn.ui.displayElement.add({
html: `<div>→</div>`,
position: { x: 0, y: 0 },
anchor: {
keyCode: 'D',
offset: { x: 70, y: 0 },
},
});anchor.keyCode is a canonical slot identifier. For a multi-key slot, use its
canonical string (e.g. "LEFT CTRL+Z" for match: 'all', "F|NUMPAD 4"
for match: 'any'). See
Canonical slot identifiers.
Cleanup
Remove every display element owned by the current plugin during cleanup.
dmn.plugin.registerCleanup(() => {
dmn.ui.displayElement.clearMyElements();
});Dialog
Modal dialogs for user interaction.
alert
Show simple alert. The returned Promise resolves when the user presses the confirm button or the dialog is dismissed.
await dmn.ui.dialog.alert('Saved!');
// Runs after the user confirms
await dmn.ui.dialog.alert('Task complete', { confirmText: 'OK' });confirm
Show confirm/cancel dialog.
Options: confirmText (confirm button label), cancelText (cancel button label), danger (renders destructive confirmations in danger colors)
const ok = await dmn.ui.dialog.confirm('Proceed?');
if (ok) {
// Confirm clicked
}
// Destructive action
const confirmed = await dmn.ui.dialog.confirm('All data will be deleted.', {
danger: true,
confirmText: 'Delete',
cancelText: 'Keep',
});custom
Shows a custom HTML dialog. Use the Components API to build controls that match the app UI.
const formHtml = `
<div class="flex flex-col gap-[12px]">
${dmn.ui.components.formRow(
'Name',
dmn.ui.components.input({ id: 'name' }),
)}
${dmn.ui.components.formRow(
'Theme',
dmn.ui.components.dropdown({
id: 'theme',
options: [
{ label: 'Dark', value: 'dark' },
{ label: 'Light', value: 'light' },
],
}),
)}
</div>
`;
const confirmed = await dmn.ui.dialog.custom(formHtml, {
confirmText: 'Save',
cancelText: 'Cancel',
showCancel: true,
});Components
These helpers return HTML strings for dialogs and display elements.
button
const saveButton = dmn.ui.components.button('Save', {
variant: 'primary',
onClick: () => console.log('Save clicked'),
});checkbox
const enabledCheckbox = dmn.ui.components.checkbox({
checked: true,
id: 'enabled',
onChange: (checked) => console.log('Enabled:', checked),
});input
const volumeInput = dmn.ui.components.input({
type: 'number',
value: 50,
min: 0,
max: 100,
width: 100,
id: 'volume',
onChange: (value) => console.log('Volume:', value),
});dropdown
const themeDropdown = dmn.ui.components.dropdown({
options: [
{ label: 'Dark', value: 'dark' },
{ label: 'Light', value: 'light' },
],
selected: 'dark',
id: 'theme',
onChange: (value) => console.log('Theme:', value),
});formRow
Pairs a label with a component.
const row = dmn.ui.components.formRow('Volume', volumeInput);panel
Wraps content for use inside a display element. Do not use this wrapper inside a dialog.
const panelHtml = dmn.ui.components.panel(row, {
title: 'Settings',
width: 400,
});
dmn.ui.displayElement.add({
html: panelHtml,
position: { x: 100, y: 100 },
});Color Picker
pickColor(options)
Opens the app color picker.
dmn.ui.pickColor({
initialColor: '#ff0000',
onColorChange: (color) => {
console.log('Preview color:', color);
},
onColorChangeComplete: (color) => {
console.log('Committed color:', color);
},
referenceElement: document.getElementById('color-button'),
onClose: () => console.log('Picker closed'),
});Use position: { x, y } when there is no reference element. The optional id prevents duplicate pickers for the same owner.