diff --git a/development/issues/closed/277.mjs b/development/issues/closed/277.mjs
index 14d7e0da7dd8256cff6c6e2dec051a32f1946696..9bb8d6ba8459fa436638e3a8fe971998f9d7cc5e 100644
--- a/development/issues/closed/277.mjs
+++ b/development/issues/closed/277.mjs
@@ -1,9 +1,9 @@
 /**
-* @file development/issues/open/277.mjs
-* @url https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/277
-* @description standard method for multiselect datatable elements
-* @issue 277
-*/
+ * @file development/issues/open/277.mjs
+ * @url https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/277
+ * @description standard method for multiselect datatable elements
+ * @issue 277
+ */
 
 import "../../../source/components/style/property.pcss";
 import "../../../source/components/style/link.pcss";
@@ -18,6 +18,7 @@ import "../../../source/components/datatable/status.mjs";
 import "../../../source/components/datatable/change-button.mjs";
 import "../../../source/components/datatable/save-button.mjs";
 import "../../../source/components/datatable/pagination.mjs";
+import "../../../source/components/datatable/embedded-pagination.mjs";
 import "../../../source/components/datatable/filter.mjs";
 import "../../../source/components/form/button.mjs";
 import "../../../source/components/form/form.mjs";
@@ -37,7 +38,7 @@ import "../../../source/components/notify/monitor-attribute-errors.mjs";
 const actionButton = document.getElementById("action-button-1")
 const datatable = document.getElementById("dt277");
 
-const eventHandler = function() {
+const eventHandler = function () {
     const selected = datatable.getSelectedRows();
 
     if (selected.length === 0) {
diff --git a/source/components/datatable/datatable.mjs b/source/components/datatable/datatable.mjs
index 16fbfa1ec386dad5683c8877c340bf27e0e15eaa..6b0fac8bfc07da370d5a8dbd46fe6262a9e9a3c4 100644
--- a/source/components/datatable/datatable.mjs
+++ b/source/components/datatable/datatable.mjs
@@ -12,79 +12,79 @@
  * SPDX-License-Identifier: AGPL-3.0
  */
 
-import { Datasource } from "./datasource.mjs";
+import {Datasource} from "./datasource.mjs";
 import {
-	assembleMethodSymbol,
-	CustomElement,
-	registerCustomElement,
-	getSlottedElements,
+    assembleMethodSymbol,
+    CustomElement,
+    registerCustomElement,
+    getSlottedElements,
 } from "../../dom/customelement.mjs";
 import {
-	findTargetElementFromEvent,
-	fireCustomEvent,
+    findTargetElementFromEvent,
+    fireCustomEvent,
 } from "../../dom/events.mjs";
-import { clone } from "../../util/clone.mjs";
+import {clone} from "../../util/clone.mjs";
 import {
-	isString,
-	isFunction,
-	isInstance,
-	isObject,
-	isArray,
+    isString,
+    isFunction,
+    isInstance,
+    isObject,
+    isArray,
 } from "../../types/is.mjs";
 import {
-	validateArray,
-	validateInteger,
-	validateObject,
+    validateArray,
+    validateInteger,
+    validateObject,
 } from "../../types/validate.mjs";
-import { Observer } from "../../types/observer.mjs";
+import {Observer} from "../../types/observer.mjs";
 import {
-	ATTRIBUTE_DATATABLE_HEAD,
-	ATTRIBUTE_DATATABLE_GRID_TEMPLATE,
-	ATTRIBUTE_DATASOURCE_SELECTOR,
-	ATTRIBUTE_DATATABLE_ALIGN,
-	ATTRIBUTE_DATATABLE_SORTABLE,
-	ATTRIBUTE_DATATABLE_MODE,
-	ATTRIBUTE_DATATABLE_INDEX,
-	ATTRIBUTE_DATATABLE_MODE_HIDDEN,
-	ATTRIBUTE_DATATABLE_FEATURES,
-	ATTRIBUTE_DATATABLE_MODE_VISIBLE,
-	ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
-	ATTRIBUTE_DATATABLE_MODE_FIXED,
+    ATTRIBUTE_DATATABLE_HEAD,
+    ATTRIBUTE_DATATABLE_GRID_TEMPLATE,
+    ATTRIBUTE_DATASOURCE_SELECTOR,
+    ATTRIBUTE_DATATABLE_ALIGN,
+    ATTRIBUTE_DATATABLE_SORTABLE,
+    ATTRIBUTE_DATATABLE_MODE,
+    ATTRIBUTE_DATATABLE_INDEX,
+    ATTRIBUTE_DATATABLE_MODE_HIDDEN,
+    ATTRIBUTE_DATATABLE_FEATURES,
+    ATTRIBUTE_DATATABLE_MODE_VISIBLE,
+    ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
+    ATTRIBUTE_DATATABLE_MODE_FIXED,
 } from "./constants.mjs";
-import { instanceSymbol } from "../../constants.mjs";
+import {instanceSymbol} from "../../constants.mjs";
 import {
-	Header,
-	createOrderStatement,
-	DIRECTION_ASC,
-	DIRECTION_DESC,
-	DIRECTION_NONE,
+    Header,
+    createOrderStatement,
+    DIRECTION_ASC,
+    DIRECTION_DESC,
+    DIRECTION_NONE,
 } from "./datatable/header.mjs";
-import { DatatableStyleSheet } from "./stylesheet/datatable.mjs";
+import {DatatableStyleSheet} from "./stylesheet/datatable.mjs";
 import {
-	handleDataSourceChanges,
-	datasourceLinkedElementSymbol,
+    handleDataSourceChanges,
+    datasourceLinkedElementSymbol,
 } from "./util.mjs";
 import "./columnbar.mjs";
 import "./filter-button.mjs";
 import {
-	findElementWithSelectorUpwards,
-	getDocument,
-	getWindow,
+    findElementWithSelectorUpwards,
+    getDocument,
+    getWindow,
 } from "../../dom/util.mjs";
-import { addAttributeToken } from "../../dom/attributes.mjs";
-import { ATTRIBUTE_ERRORMESSAGE } from "../../dom/constants.mjs";
-import { getDocumentTranslations } from "../../i18n/translations.mjs";
+
+import {getDocumentTranslations} from "../../i18n/translations.mjs";
 import "../state/state.mjs";
 import "../host/collapse.mjs";
-import { generateUniqueConfigKey } from "../host/util.mjs";
+import {generateUniqueConfigKey} from "../host/util.mjs";
 
 import "./datasource/dom.mjs";
 import "./datasource/rest.mjs";
 
 import "../form/context-help.mjs";
-import { getLocaleOfDocument } from "../../dom/locale.mjs";
+import {getLocaleOfDocument} from "../../dom/locale.mjs";
+import {addErrorAttribute} from "../../dom/error.mjs";
 
-export { DataTable };
+export {DataTable};
 
 /**
  * @private
@@ -150,419 +150,421 @@ const resizeObserverSymbol = Symbol("resizeObserver");
  * @fires monster-datatable-selection-changed
  **/
 class DataTable extends CustomElement {
-	/**
-	 * This method is called by the `instanceof` operator.
-	 * @return {symbol}
-	 */
-	static get [instanceSymbol]() {
-		return Symbol.for("@schukai/monster/components/datatable@@instance");
-	}
-
-	/**
-	 * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
-	 * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
-	 *
-	 * The individual configuration values can be found in the table.
-	 *
-	 * @property {Object} templates Template definitions
-	 * @property {string} templates.main Main template
-	 * @property {Object} datasource Datasource configuration
-	 * @property {string} datasource.selector Selector for the datasource
-	 * @property {Object} mapping Mapping configuration
-	 * @property {string} mapping.data Data mapping
-	 * @property {Array} data Data
-	 * @property {Array} headers Headers
-	 * @property {Object} responsive Responsive configuration
-	 * @property {number} responsive.breakpoint Breakpoint for responsive mode
-	 * @property {Object} labels Labels
-	 * @property {string} labels.theListContainsNoEntries Label for empty state
-	 * @property {Object} classes Classes
-	 * @property {string} classes.container Container class
-	 * @property {Object} features Features
-	 * @property {boolean} features.settings Settings feature
-	 * @property {boolean} features.footer Footer feature
-	 * @property {boolean} features.autoInit Auto init feature (init datasource automatically)
-	 * @property {boolean} features.doubleClickCopyToClipboard Double click copy to clipboard feature
-	 * @property {boolean} features.copyAll Copy all feature
-	 * @property {boolean} features.help Help feature
-	 * @property {Object} templateMapping Template mapping
-	 * @property {string} templateMapping.row-key Row key
-	 * @property {string} templateMapping.filter-id Filter id
-	 **/
-	get defaults() {
-		return Object.assign(
-			{},
-			super.defaults,
-			{
-				templates: {
-					main: getTemplate(),
-					emptyState: getEmptyTemplate(),
-				},
-
-				datasource: {
-					selector: null,
-				},
-
-				mapping: {
-					data: "dataset",
-				},
-
-				data: [],
-				headers: [],
-
-				responsive: {
-					breakpoint: 900,
-				},
-
-				labels: getTranslations(),
-
-				classes: {
-					control: "monster-theme-control-container-1",
-					container: "",
-					row: "monster-theme-control-row-1",
-				},
-
-				features: {
-					settings: true,
-					footer: true,
-					autoInit: true,
-					doubleClickCopyToClipboard: true,
-					copyAll: true,
-					help: true,
-				},
-
-				copy: {
-					delimiter: ";",
-					quoteOpen: '"',
-					quoteClose: '"',
-					rowBreak: "\n",
-				},
-
-				templateMapping: {
-					"row-key": null,
-					"filter-id": null,
-				},
-			},
-			initOptionsFromArguments.call(this),
-		);
-	}
-
-	/**
-	 *
-	 * @param {string} selector
-	 * @return {NodeList}
-	 */
-	getGridElements(selector) {
-		return this[gridElementSymbol].querySelectorAll(selector);
-	}
-
-	/**
-	 *
-	 * @return {string}
-	 */
-	static getTag() {
-		return "monster-datatable";
-	}
-
-	/**
-	 * @return {void}
-	 */
-	disconnectedCallback() {
-		super.disconnectedCallback();
-		if (this?.[resizeObserverSymbol] instanceof ResizeObserver) {
-			this[resizeObserverSymbol].disconnect();
-		}
-	}
-
-	/**
-	 * @return {void}
-	 */
-	connectedCallback() {
-		const self = this;
-		super.connectedCallback();
-
-		this[resizeObserverSymbol] = new ResizeObserver((entries) => {
-			updateGrid.call(self);
-		});
-
-		this[resizeObserverSymbol].observe(this.parentNode);
-	}
-
-	/**
-	 * Get the row number of the selected rows as an array
-	 *
-	 * @returns {number[]}
-	 */
-	getSelectedRows() {
-		const rows = this.getGridElements(`[data-monster-role="select-row"]`);
-		const selectedRows = [];
-		rows.forEach((row) => {
-			if (row.checked) {
-				const key = row.parentNode.getAttribute(
-					"data-monster-insert-reference",
-				);
-				const index = key.split("-").pop();
-				selectedRows.push(parseInt(index, 10));
-			}
-		});
-
-		return selectedRows;
-	}
-
-	/**
-	 * @return void
-	 */
-	[assembleMethodSymbol]() {
-		const rawKey = this.getOption("templateMapping.row-key");
-
-		if (rawKey === null) {
-			if (this.id !== null && this.id !== "") {
-				const rawKey = this.getOption("templateMapping.row-key");
-				if (rawKey === null) {
-					this.setOption("templateMapping.row-key", this.id + "-row");
-				}
-			} else {
-				this.setOption("templateMapping.row-key", "row");
-			}
-		}
-
-		if (this.id !== null && this.id !== "") {
-			this.setOption("templateMapping.filter-id", "" + this.id + "-filter");
-		} else {
-			this.setOption("templateMapping.filter-id", "filter");
-		}
-
-		super[assembleMethodSymbol]();
-
-		initControlReferences.call(this);
-		initEventHandler.call(this);
-
-		const selector = this.getOption("datasource.selector");
-
-		if (isString(selector)) {
-			const element = findElementWithSelectorUpwards(this, selector);
-			if (element === null) {
-				throw new Error("the selector must match exactly one element");
-			}
-
-			if (!isInstance(element, Datasource)) {
-				throw new TypeError("the element must be a datasource");
-			}
-
-			this[datasourceLinkedElementSymbol] = element;
-
-			queueMicrotask(() => {
-				handleDataSourceChanges.call(this);
-				element.datasource.attachObserver(
-					new Observer(handleDataSourceChanges.bind(this)),
-				);
-			});
-		}
-
-		getHostConfig
-			.call(this, getColumnVisibilityConfigKey)
-			.then((config) => {
-				const headerOrderMap = new Map();
-
-				getHostConfig
-					.call(this, getStoredOrderConfigKey)
-					.then((orderConfig) => {
-						if (isArray(orderConfig) || orderConfig.length > 0) {
-							for (let i = 0; i < orderConfig.length; i++) {
-								const item = orderConfig[i];
-								const parts = item.split(" ");
-								const field = parts[0];
-								const direction = parts[1] || DIRECTION_ASC;
-								headerOrderMap.set(field, direction);
-							}
-						}
-					})
-					.then(() => {
-						try {
-							initGridAndStructs.call(this, config, headerOrderMap);
-						} catch (error) {
-							addAttributeToken(
-								this,
-								ATTRIBUTE_ERRORMESSAGE,
-								error?.message || error.toString(),
-							);
-						}
-
-						updateColumnBar.call(this);
-					})
-					.catch((error) => {
-						addAttributeToken(
-							this,
-							ATTRIBUTE_ERRORMESSAGE,
-							error?.message || error.toString(),
-						);
-					});
-			})
-			.catch((error) => {
-				addAttributeToken(
-					this,
-					ATTRIBUTE_ERRORMESSAGE,
-					error?.message || error.toString(),
-				);
-			});
-	}
-
-	/**
-	 * @return {CSSStyleSheet[]}
-	 */
-	static getCSSStyleSheet() {
-		return [DatatableStyleSheet];
-	}
-
-	/**
-	 * Copy a row from the datatable
-	 *
-	 * @param {number|string} fromIndex
-	 * @param {number|string} toIndex
-	 * @return {DataTable}
-	 * @fires monster-datatable-row-copied
-	 */
-	copyRow(fromIndex, toIndex) {
-		const datasource = this[datasourceLinkedElementSymbol];
-		if (!datasource) {
-			return this;
-		}
-		let d = datasource.data;
-		let c = clone(d);
-
-		let rows = c;
-		const mapping = this.getOption("mapping.data");
-
-		if (mapping) {
-			rows = c?.[mapping];
-		}
-
-		if (rows === undefined || rows === null) {
-			rows = [];
-		}
-
-		if (toIndex === undefined) {
-			toIndex = rows.length;
-		}
-
-		if (isString(fromIndex)) {
-			fromIndex = parseInt(fromIndex);
-		}
-		if (isString(toIndex)) {
-			toIndex = parseInt(toIndex);
-		}
-
-		if (toIndex < 0 || toIndex > rows.length) {
-			throw new RangeError("index out of bounds");
-		}
-
-		validateArray(rows);
-		validateInteger(fromIndex);
-		validateInteger(toIndex);
-
-		if (fromIndex < 0 || fromIndex >= rows.length) {
-			throw new RangeError("index out of bounds");
-		}
-
-		rows.splice(toIndex, 0, clone(rows[fromIndex]));
-		datasource.data = c;
-
-		fireCustomEvent(this, "monster-datatable-row-copied", {
-			index: toIndex,
-		});
-
-		return this;
-	}
-
-	/**
-	 * Remove a row from the datatable
-	 *
-	 * @param {number|string} index
-	 * @return {DataTable}
-	 * @fires monster-datatable-row-removed
-	 */
-	removeRow(index) {
-		const datasource = this[datasourceLinkedElementSymbol];
-		if (!datasource) {
-			return this;
-		}
-		let d = datasource.data;
-		let c = clone(d);
-
-		let rows = c;
-		const mapping = this.getOption("mapping.data");
-
-		if (mapping) {
-			rows = c?.[mapping];
-		}
-
-		if (rows === undefined || rows === null) {
-			rows = [];
-		}
-
-		if (isString(index)) {
-			index = parseInt(index);
-		}
-
-		validateArray(rows);
-		validateInteger(index);
-
-		if (index < 0 || index >= rows.length) {
-			throw new RangeError("index out of bounds");
-		}
-		if (mapping) {
-			rows = c?.[mapping];
-		}
-
-		rows.splice(index, 1);
-		datasource.data = c;
-
-		fireCustomEvent(this, "monster-datatable-row-removed", {
-			index: index,
-		});
-
-		return this;
-	}
-
-	/**
-	 * Add a row to the datatable
-	 *
-	 * @param {Object} data
-	 * @return {DataTable}
-	 *
-	 * @fires monster-datatable-row-added
-	 **/
-	addRow(data) {
-		const datasource = this[datasourceLinkedElementSymbol];
-		if (!datasource) {
-			return this;
-		}
-		let d = datasource.data;
-		let c = clone(d);
-
-		let rows = c;
-
-		const mapping = this.getOption("mapping.data");
-		if (mapping) {
-			rows = c?.[mapping];
-		}
-
-		if (rows === undefined || rows === null) {
-			rows = [];
-		}
-
-		validateArray(rows);
-		validateObject(data);
-
-		rows.push(data);
-		datasource.data = c;
-
-		fireCustomEvent(this, "monster-datatable-row-added", {
-			index: rows.length - 1,
-		});
-
-		return this;
-	}
+    /**
+     * This method is called by the `instanceof` operator.
+     * @return {symbol}
+     */
+    static get [instanceSymbol]() {
+        return Symbol.for("@schukai/monster/components/datatable@@instance");
+    }
+
+    /**
+     * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
+     * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
+     *
+     * The individual configuration values can be found in the table.
+     *
+     * @property {Object} templates Template definitions
+     * @property {string} templates.main Main template
+     * @property {Object} datasource Datasource configuration
+     * @property {string} datasource.selector Selector for the datasource
+     * @property {Object} mapping Mapping configuration
+     * @property {string} mapping.data Data mapping
+     * @property {Array} data Data
+     * @property {Array} headers Headers
+     * @property {Object} responsive Responsive configuration
+     * @property {number} responsive.breakpoint Breakpoint for responsive mode
+     * @property {Object} labels Labels
+     * @property {string} labels.theListContainsNoEntries Label for empty state
+     * @property {Object} classes Classes
+     * @property {string} classes.container Container class
+     * @property {Object} features Features
+     * @property {boolean} features.settings Settings feature
+     * @property {boolean} features.footer Footer feature
+     * @property {boolean} features.autoInit Auto init feature (init datasource automatically)
+     * @property {boolean} features.doubleClickCopyToClipboard Double click copy to clipboard feature
+     * @property {boolean} features.copyAll Copy all feature
+     * @property {boolean} features.help Help feature
+     * @property {Object} templateMapping Template mapping
+     * @property {string} templateMapping.row-key Row key
+     * @property {string} templateMapping.filter-id Filter id
+     **/
+    get defaults() {
+        return Object.assign(
+            {},
+            super.defaults,
+            {
+                templates: {
+                    main: getTemplate(),
+                    emptyState: getEmptyTemplate(),
+                },
+
+                datasource: {
+                    selector: null,
+                },
+
+                mapping: {
+                    data: "dataset",
+                },
+
+                data: [],
+                headers: [],
+
+                responsive: {
+                    breakpoint: 900,
+                },
+
+                labels: getTranslations(),
+
+                classes: {
+                    control: "monster-theme-control-container-1",
+                    container: "",
+                    row: "monster-theme-control-row-1",
+                },
+
+                features: {
+                    settings: true,
+                    footer: true,
+                    autoInit: true,
+                    doubleClickCopyToClipboard: true,
+                    copyAll: true,
+                    help: true,
+                },
+
+                copy: {
+                    delimiter: ";",
+                    quoteOpen: '"',
+                    quoteClose: '"',
+                    rowBreak: "\n",
+                },
+
+                templateMapping: {
+                    "row-key": null,
+                    "filter-id": null,
+                },
+            },
+            initOptionsFromArguments.call(this),
+        );
+    }
+
+    /**
+     *
+     * @param {string} selector
+     * @return {NodeList}
+     */
+    getGridElements(selector) {
+        return this[gridElementSymbol].querySelectorAll(selector);
+    }
+
+    /**
+     *
+     * @return {string}
+     */
+    static getTag() {
+        return "monster-datatable";
+    }
+
+    /**
+     * @return {void}
+     */
+    disconnectedCallback() {
+        super.disconnectedCallback();
+        if (this?.[resizeObserverSymbol] instanceof ResizeObserver) {
+            this[resizeObserverSymbol].disconnect();
+        }
+    }
+
+    /**
+     * @return {void}
+     */
+    connectedCallback() {
+        const self = this;
+        super.connectedCallback();
+
+        this[resizeObserverSymbol] = new ResizeObserver((entries) => {
+            updateGrid.call(self);
+        });
+
+        this[resizeObserverSymbol].observe(this.parentNode);
+    }
+
+    /**
+     * Get the row number of the selected rows as an array
+     *
+     * @returns {number[]}
+     */
+    getSelectedRows() {
+        const rows = this.getGridElements(`[data-monster-role="select-row"]`);
+        const selectedRows = [];
+        rows.forEach((row) => {
+            if (row.checked) {
+                const key = row.parentNode.getAttribute(
+                    "data-monster-insert-reference",
+                );
+                const index = key.split("-").pop();
+                selectedRows.push(parseInt(index, 10));
+            }
+        });
+
+        return selectedRows;
+    }
+
+    /**
+     * @return void
+     */
+    [assembleMethodSymbol]() {
+        const rawKey = this.getOption("templateMapping.row-key");
+
+        if (rawKey === null) {
+            if (this.id !== null && this.id !== "") {
+                const rawKey = this.getOption("templateMapping.row-key");
+                if (rawKey === null) {
+                    this.setOption("templateMapping.row-key", this.id + "-row");
+                }
+            } else {
+                this.setOption("templateMapping.row-key", "row");
+            }
+        }
+
+        if (this.id !== null && this.id !== "") {
+            this.setOption("templateMapping.filter-id", "" + this.id + "-filter");
+        } else {
+            this.setOption("templateMapping.filter-id", "filter");
+        }
+
+        super[assembleMethodSymbol]();
+
+        initControlReferences.call(this);
+        initEventHandler.call(this);
+
+        getHostConfig
+            .call(this, getColumnVisibilityConfigKey)
+            .then((config) => {
+                const headerOrderMap = new Map();
+
+                getHostConfig
+                    .call(this, getStoredOrderConfigKey)
+                    .then((orderConfig) => {
+                        if (isArray(orderConfig) || orderConfig.length > 0) {
+                            for (let i = 0; i < orderConfig.length; i++) {
+                                const item = orderConfig[i];
+                                const parts = item.split(" ");
+                                const field = parts[0];
+                                const direction = parts[1] || DIRECTION_ASC;
+                                headerOrderMap.set(field, direction);
+                            }
+                        }
+                    })
+                    .then(() => {
+                        try {
+                            initGridAndStructs.call(this, config, headerOrderMap);
+                        } catch (error) {
+                            addErrorAttribute(
+                                this,
+                                error
+                            );
+                        }
+
+                        updateColumnBar.call(this);
+                    })
+                    .catch((error) => {
+                        addErrorAttribute(
+                            this, error,
+                        );
+                    });
+            })
+            .catch((error) => {
+                addErrorAttribute(
+                    this,
+                    error
+                );
+            })
+            .finally(() => {
+
+                const selector = this.getOption("datasource.selector");
+
+                if (isString(selector)) {
+                    const element = findElementWithSelectorUpwards(this, selector);
+                    if (element === null) {
+                        throw new Error("the selector must match exactly one element");
+                    }
+
+                    if (!isInstance(element, Datasource)) {
+                        throw new TypeError("the element must be a datasource");
+                    }
+
+                    this[datasourceLinkedElementSymbol] = element;
+
+                    getWindow().requestAnimationFrame(() => {
+
+                        handleDataSourceChanges.call(this);
+                        if (element && "datasource" in element) {
+                            element.datasource.attachObserver(
+                                new Observer(handleDataSourceChanges.bind(this)),
+                            );
+                        }
+                    });
+                }
+            });
+
+    }
+
+    /**
+     * @return {CSSStyleSheet[]}
+     */
+    static getCSSStyleSheet() {
+        return [DatatableStyleSheet];
+    }
+
+    /**
+     * Copy a row from the datatable
+     *
+     * @param {number|string} fromIndex
+     * @param {number|string} toIndex
+     * @return {DataTable}
+     * @fires monster-datatable-row-copied
+     */
+    copyRow(fromIndex, toIndex) {
+        const datasource = this[datasourceLinkedElementSymbol];
+        if (!datasource) {
+            return this;
+        }
+        let d = datasource.data;
+        let c = clone(d);
+
+        let rows = c;
+        const mapping = this.getOption("mapping.data");
+
+        if (mapping) {
+            rows = c?.[mapping];
+        }
+
+        if (rows === undefined || rows === null) {
+            rows = [];
+        }
+
+        if (toIndex === undefined) {
+            toIndex = rows.length;
+        }
+
+        if (isString(fromIndex)) {
+            fromIndex = parseInt(fromIndex);
+        }
+        if (isString(toIndex)) {
+            toIndex = parseInt(toIndex);
+        }
+
+        if (toIndex < 0 || toIndex > rows.length) {
+            throw new RangeError("index out of bounds");
+        }
+
+        validateArray(rows);
+        validateInteger(fromIndex);
+        validateInteger(toIndex);
+
+        if (fromIndex < 0 || fromIndex >= rows.length) {
+            throw new RangeError("index out of bounds");
+        }
+
+        rows.splice(toIndex, 0, clone(rows[fromIndex]));
+        datasource.data = c;
+
+        fireCustomEvent(this, "monster-datatable-row-copied", {
+            index: toIndex,
+        });
+
+        return this;
+    }
+
+    /**
+     * Remove a row from the datatable
+     *
+     * @param {number|string} index
+     * @return {DataTable}
+     * @fires monster-datatable-row-removed
+     */
+    removeRow(index) {
+        const datasource = this[datasourceLinkedElementSymbol];
+        if (!datasource) {
+            return this;
+        }
+        let d = datasource.data;
+        let c = clone(d);
+
+        let rows = c;
+        const mapping = this.getOption("mapping.data");
+
+        if (mapping) {
+            rows = c?.[mapping];
+        }
+
+        if (rows === undefined || rows === null) {
+            rows = [];
+        }
+
+        if (isString(index)) {
+            index = parseInt(index);
+        }
+
+        validateArray(rows);
+        validateInteger(index);
+
+        if (index < 0 || index >= rows.length) {
+            throw new RangeError("index out of bounds");
+        }
+        if (mapping) {
+            rows = c?.[mapping];
+        }
+
+        rows.splice(index, 1);
+        datasource.data = c;
+
+        fireCustomEvent(this, "monster-datatable-row-removed", {
+            index: index,
+        });
+
+        return this;
+    }
+
+    /**
+     * Add a row to the datatable
+     *
+     * @param {Object} data
+     * @return {DataTable}
+     *
+     * @fires monster-datatable-row-added
+     **/
+    addRow(data) {
+        const datasource = this[datasourceLinkedElementSymbol];
+        if (!datasource) {
+            return this;
+        }
+        let d = datasource.data;
+        let c = clone(d);
+
+        let rows = c;
+
+        const mapping = this.getOption("mapping.data");
+        if (mapping) {
+            rows = c?.[mapping];
+        }
+
+        if (rows === undefined || rows === null) {
+            rows = [];
+        }
+
+        validateArray(rows);
+        validateObject(data);
+
+        rows.push(data);
+        datasource.data = c;
+
+        fireCustomEvent(this, "monster-datatable-row-added", {
+            index: rows.length - 1,
+        });
+
+        return this;
+    }
 }
 
 /**
@@ -570,7 +572,7 @@ class DataTable extends CustomElement {
  * @return {string}
  */
 function getColumnVisibilityConfigKey() {
-	return generateUniqueConfigKey("datatable", this?.id, "columns-visibility");
+    return generateUniqueConfigKey("datatable", this?.id, "columns-visibility");
 }
 
 /**
@@ -578,7 +580,7 @@ function getColumnVisibilityConfigKey() {
  * @return {string}
  */
 function getFilterConfigKey() {
-	return generateUniqueConfigKey("datatable", this?.id, "filter");
+    return generateUniqueConfigKey("datatable", this?.id, "filter");
 }
 
 /**
@@ -586,519 +588,529 @@ function getFilterConfigKey() {
  * @return {Promise}
  */
 function getHostConfig(callback) {
-	const host = findElementWithSelectorUpwards(this, "monster-host");
-
-	if (!host) {
-		addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "no host found");
-		return Promise.resolve({});
-	}
-
-	if (!this.id) {
-		addAttributeToken(
-			this,
-			ATTRIBUTE_ERRORMESSAGE,
-			"no id found; id is required for config",
-		);
-		return Promise.resolve({});
-	}
-
-	if (!host || !isFunction(host?.getConfig)) {
-		throw new TypeError("the host must be a monster-host");
-	}
-
-	const configKey = callback.call(this);
-	return host.hasConfig(configKey).then((hasConfig) => {
-		if (hasConfig) {
-			return host.getConfig(configKey);
-		} else {
-			return {};
-		}
-	});
+    const host = findElementWithSelectorUpwards(this, "monster-host");
+
+    if (!host) {
+        addErrorAttribute(this, "no host found");
+        return Promise.resolve({});
+    }
+
+    if (!this.id) {
+        addErrorAttribute(
+            this,
+            "no id found; id is required for config",
+        );
+        return Promise.resolve({});
+    }
+
+    if (!host || !isFunction(host?.getConfig)) {
+        throw new TypeError("the host must be a monster-host");
+    }
+
+    const configKey = callback.call(this);
+    return host.hasConfig(configKey).then((hasConfig) => {
+        if (hasConfig) {
+            return host.getConfig(configKey);
+        } else {
+            return {};
+        }
+    });
 }
 
 /**
  * @private
  */
 function updateColumnBar() {
-	if (!this[columnBarElementSymbol]) {
-		return;
-	}
-
-	const columns = [];
-	for (const header of this.getOption("headers")) {
-		const mode = header.getInternal("mode");
-
-		if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
-			continue;
-		}
-
-		columns.push({
-			visible: mode !== ATTRIBUTE_DATATABLE_MODE_HIDDEN,
-			name: header.label,
-			index: header.index,
-		});
-	}
-
-	this[columnBarElementSymbol].setOption("columns", columns);
+    if (!this[columnBarElementSymbol]) {
+        return;
+    }
+
+    const columns = [];
+    for (const header of this.getOption("headers")) {
+        const mode = header.getInternal("mode");
+
+        if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
+            continue;
+        }
+
+        columns.push({
+            visible: mode !== ATTRIBUTE_DATATABLE_MODE_HIDDEN,
+            name: header.label,
+            index: header.index,
+        });
+    }
+
+    this[columnBarElementSymbol].setOption("columns", columns);
 }
 
 /**
  * @private
  */
 function updateHeaderFromColumnBar() {
-	if (!this[columnBarElementSymbol]) {
-		return;
-	}
+    if (!this[columnBarElementSymbol]) {
+        return;
+    }
 
-	const options = this[columnBarElementSymbol].getOption("columns");
-	if (!isArray(options)) return;
+    const options = this[columnBarElementSymbol].getOption("columns");
+    if (!isArray(options)) return;
 
-	const invisibleMap = {};
+    const invisibleMap = {};
 
-	for (let i = 0; i < options.length; i++) {
-		const option = options[i];
-		invisibleMap[option.index] = option.visible;
-	}
+    for (let i = 0; i < options.length; i++) {
+        const option = options[i];
+        invisibleMap[option.index] = option.visible;
+    }
 
-	for (const header of this.getOption("headers")) {
-		const mode = header.getInternal("mode");
+    for (const header of this.getOption("headers")) {
+        const mode = header.getInternal("mode");
 
-		if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
-			continue;
-		}
+        if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) {
+            continue;
+        }
 
-		if (invisibleMap[header.index] === false) {
-			header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_HIDDEN);
-		} else {
-			header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_VISIBLE);
-		}
-	}
+        if (invisibleMap[header.index] === false) {
+            header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_HIDDEN);
+        } else {
+            header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_VISIBLE);
+        }
+    }
 }
 
 /**
  * @private
  */
 function updateConfigColumnBar() {
-	if (!this[columnBarElementSymbol]) {
-		return;
-	}
-
-	const options = this[columnBarElementSymbol].getOption("columns");
-	if (!isArray(options)) return;
-
-	const map = {};
-	for (let i = 0; i < options.length; i++) {
-		const option = options[i];
-		map[option.name] = option.visible;
-	}
-
-	const host = findElementWithSelectorUpwards(this, "monster-host");
-	if (!(host && this.id)) {
-		return;
-	}
-	const configKey = getColumnVisibilityConfigKey.call(this);
-
-	try {
-		host.setConfig(configKey, map);
-	} catch (error) {
-		addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, String(error));
-	}
+    if (!this[columnBarElementSymbol]) {
+        return;
+    }
+
+    const options = this[columnBarElementSymbol].getOption("columns");
+    if (!isArray(options)) return;
+
+    const map = {};
+    for (let i = 0; i < options.length; i++) {
+        const option = options[i];
+        map[option.name] = option.visible;
+    }
+
+    const host = findElementWithSelectorUpwards(this, "monster-host");
+    if (!(host && this.id)) {
+        return;
+    }
+    const configKey = getColumnVisibilityConfigKey.call(this);
+
+    try {
+        host.setConfig(configKey, map);
+    } catch (error) {
+        addErrorAttribute(this, error);
+    }
 }
 
 /**
  * @private
  */
 function initEventHandler() {
-	const self = this;
-
-	const quoteOpenChar = this.getOption("copy.quoteOpen");
-	const quoteCloseChar = this.getOption("copy.quoteClose");
-	const delimiterChar = this.getOption("copy.delimiter");
-	const rowBreak = this.getOption("copy.rowBreak");
-
-	self[columnBarElementSymbol].attachObserver(
-		new Observer((e) => {
-			updateHeaderFromColumnBar.call(self);
-			updateGrid.call(self);
-			updateConfigColumnBar.call(self);
-		}),
-	);
-
-	self[gridHeadersElementSymbol].addEventListener("click", function (event) {
-		let element = null;
-		const datasource = self[datasourceLinkedElementSymbol];
-		if (!datasource) {
-			return;
-		}
-
-		element = findTargetElementFromEvent(event, ATTRIBUTE_DATATABLE_SORTABLE);
-		if (element) {
-			const index = element.parentNode.getAttribute(ATTRIBUTE_DATATABLE_INDEX);
-			const headers = self.getOption("headers");
-
-			event.preventDefault();
-
-			headers[index].changeDirection();
-
-			queueMicrotask(function () {
-				/** hotfix, normally this should be done via the updater, no idea why this is not possible. */
-				element.setAttribute(
-					ATTRIBUTE_DATATABLE_SORTABLE,
-					`${headers[index].field} ${headers[index].direction}`,
-				);
-
-				storeOrderStatement.call(self, true);
-			});
-		}
-	});
-
-	const eventHandlerDoubleClickCopyToClipboard = (event) => {
-		const element = findTargetElementFromEvent(event, "data-monster-head");
-		if (element) {
-			let text = "";
-
-			if (event.shiftKey) {
-				const index = element.getAttribute("data-monster-insert-reference");
-				if (index) {
-					const cols = self.getGridElements(
-						`[data-monster-insert-reference="${index}"]`,
-					);
-
-					const colTexts = [];
-					for (let i = 0; i < cols.length; i++) {
-						const col = cols[i];
-
-						if (
-							col.querySelector("monster-button-bar") ||
-							col.querySelector("monster-button")
-						) {
-							continue;
-						}
-
-						if (col.textContent) {
-							colTexts.push(
-								quoteOpenChar + col.textContent.trim() + quoteCloseChar,
-							);
-						}
-					}
-
-					text = colTexts.join(delimiterChar);
-				}
-			} else {
-				if (
-					element.querySelector("monster-button-bar") ||
-					element.querySelector("monster-button")
-				) {
-					return;
-				}
-
-				text = element.textContent.trim();
-			}
-
-			if (getWindow().navigator.clipboard && text) {
-				getWindow()
-					.navigator.clipboard.writeText(text)
-					.then(
-						() => {},
-						(err) => {},
-					);
-			}
-		}
-	};
-
-	if (self.getOption("features.doubleClickCopyToClipboard")) {
-		self[gridElementSymbol].addEventListener(
-			"dblclick",
-			eventHandlerDoubleClickCopyToClipboard,
-		);
-	}
-
-	if (self.getOption("features.copyAll") && this[copyAllElementSymbol]) {
-		this[copyAllElementSymbol].addEventListener("click", (event) => {
-			event.preventDefault();
-
-			const table = [];
-			let currentRow = [];
-			let currentIndex = null;
-
-			const cols = self.getGridElements(`[data-monster-insert-reference]`);
-			const rowIndexes = new Map();
-			cols.forEach((col) => {
-				const index = col.getAttribute("data-monster-insert-reference");
-				rowIndexes.set(index, true);
-			});
-
-			rowIndexes.forEach((value, key) => {
-				const cols = self.getGridElements(
-					`[data-monster-insert-reference="${key}"]`,
-				);
-
-				for (let i = 0; i < cols.length; i++) {
-					const col = cols[i];
-
-					if (
-						col.querySelector("monster-button-bar") ||
-						col.querySelector("monster-button")
-					) {
-						continue;
-					}
-
-					if (col.textContent) {
-						currentRow.push(
-							quoteOpenChar + col.textContent.trim() + quoteCloseChar,
-						);
-					}
-				}
-
-				if (currentRow.length > 0) {
-					table.push(currentRow);
-				}
-				currentRow = [];
-			});
-
-			if (table.length > 0) {
-				const text = table.map((row) => row.join(delimiterChar)).join(rowBreak);
-				if (getWindow().navigator.clipboard && text) {
-					getWindow()
-						.navigator.clipboard.writeText(text)
-						.then(
-							() => {},
-							(err) => {},
-						);
-				}
-			}
-		});
-	}
-
-	const selectRowCallback = (event) => {
-		const element = findTargetElementFromEvent(
-			event,
-			"data-monster-role",
-			"select-row",
-		);
-		if (element) {
-			const key = element.parentNode.getAttribute(
-				"data-monster-insert-reference",
-			);
-			const row = self.getGridElements(
-				`[data-monster-insert-reference="${key}"]`,
-			);
-
-			const index = key.split("-").pop();
-
-			if (element.checked) {
-				row.forEach((col) => {
-					col.classList.add("selected");
-				});
-
-				fireCustomEvent(self, "monster-datatable-row-selected", {
-					index: index,
-				});
-			} else {
-				row.forEach((col) => {
-					col.classList.remove("selected");
-				});
-
-				fireCustomEvent(self, "monster-datatable-row-deselected", {
-					index: index,
-				});
-			}
-
-			fireCustomEvent(this, "monster-datatable-selection-changed", {});
-		}
-
-		const rows = self.getGridElements(`[data-monster-role="select-row"]`);
-		const allSelected = Array.from(rows).every((row) => row.checked);
-		const selectAll = this[gridHeadersElementSymbol].querySelector(
-			`[data-monster-role="select-all"]`,
-		);
-		selectAll.checked = allSelected;
-	};
-
-	this[gridElementSymbol].addEventListener("click", selectRowCallback);
-	this[gridElementSymbol].addEventListener("touch", selectRowCallback);
-
-	const selectAllCallback = (event) => {
-		const element = findTargetElementFromEvent(
-			event,
-			"data-monster-role",
-			"select-all",
-		);
-		if (element) {
-			const mode = element.checked;
-
-			const rows = this.getGridElements(`[data-monster-role="select-row"]`);
-			rows.forEach((row) => {
-				row.checked = mode;
-			});
-
-			if (mode) {
-				fireCustomEvent(this, "monster-datatable-all-rows-selected", {});
-			} else {
-				fireCustomEvent(this, "monster-datatable-all-rows-deselected", {});
-			}
-
-			fireCustomEvent(this, "monster-datatable-selection-changed", {});
-		}
-	};
-
-	this[gridHeadersElementSymbol].addEventListener("click", selectAllCallback);
-	this[gridHeadersElementSymbol].addEventListener("touch", selectAllCallback);
+    const self = this;
+
+    const quoteOpenChar = this.getOption("copy.quoteOpen");
+    const quoteCloseChar = this.getOption("copy.quoteClose");
+    const delimiterChar = this.getOption("copy.delimiter");
+    const rowBreak = this.getOption("copy.rowBreak");
+
+    self[columnBarElementSymbol].attachObserver(
+        new Observer((e) => {
+            updateHeaderFromColumnBar.call(self);
+            updateGrid.call(self);
+            updateConfigColumnBar.call(self);
+        }),
+    );
+
+    self[gridHeadersElementSymbol].addEventListener("click", function (event) {
+        let element = null;
+        const datasource = self[datasourceLinkedElementSymbol];
+        if (!datasource) {
+            return;
+        }
+
+        element = findTargetElementFromEvent(event, ATTRIBUTE_DATATABLE_SORTABLE);
+        if (element) {
+            const index = element.parentNode.getAttribute(ATTRIBUTE_DATATABLE_INDEX);
+            const headers = self.getOption("headers");
+
+            event.preventDefault();
+
+            headers[index].changeDirection();
+
+            queueMicrotask(function () {
+                /** hotfix, normally this should be done via the updater, no idea why this is not possible. */
+                element.setAttribute(
+                    ATTRIBUTE_DATATABLE_SORTABLE,
+                    `${headers[index].field} ${headers[index].direction}`,
+                );
+
+                storeOrderStatement.call(self, true);
+            });
+        }
+    });
+
+    const eventHandlerDoubleClickCopyToClipboard = (event) => {
+        const element = findTargetElementFromEvent(event, "data-monster-head");
+        if (element) {
+            let text = "";
+
+            if (event.shiftKey) {
+                const index = element.getAttribute("data-monster-insert-reference");
+                if (index) {
+                    const cols = self.getGridElements(
+                        `[data-monster-insert-reference="${index}"]`,
+                    );
+
+                    const colTexts = [];
+                    for (let i = 0; i < cols.length; i++) {
+                        const col = cols[i];
+
+                        if (
+                            col.querySelector("monster-button-bar") ||
+                            col.querySelector("monster-button")
+                        ) {
+                            continue;
+                        }
+
+                        if (col.textContent) {
+                            colTexts.push(
+                                quoteOpenChar + col.textContent.trim() + quoteCloseChar,
+                            );
+                        }
+                    }
+
+                    text = colTexts.join(delimiterChar);
+                }
+            } else {
+                if (
+                    element.querySelector("monster-button-bar") ||
+                    element.querySelector("monster-button")
+                ) {
+                    return;
+                }
+
+                text = element.textContent.trim();
+            }
+
+            if (getWindow().navigator.clipboard && text) {
+                getWindow()
+                    .navigator.clipboard.writeText(text)
+                    .then(
+                        () => {
+                        },
+                        (err) => {
+                        },
+                    );
+            }
+        }
+    };
+
+    if (self.getOption("features.doubleClickCopyToClipboard")) {
+        self[gridElementSymbol].addEventListener(
+            "dblclick",
+            eventHandlerDoubleClickCopyToClipboard,
+        );
+    }
+
+    if (self.getOption("features.copyAll") && this[copyAllElementSymbol]) {
+        this[copyAllElementSymbol].addEventListener("click", (event) => {
+            event.preventDefault();
+
+            const table = [];
+            let currentRow = [];
+            let currentIndex = null;
+
+            const cols = self.getGridElements(`[data-monster-insert-reference]`);
+            const rowIndexes = new Map();
+            cols.forEach((col) => {
+                const index = col.getAttribute("data-monster-insert-reference");
+                rowIndexes.set(index, true);
+            });
+
+            rowIndexes.forEach((value, key) => {
+                const cols = self.getGridElements(
+                    `[data-monster-insert-reference="${key}"]`,
+                );
+
+                for (let i = 0; i < cols.length; i++) {
+                    const col = cols[i];
+
+                    if (
+                        col.querySelector("monster-button-bar") ||
+                        col.querySelector("monster-button")
+                    ) {
+                        continue;
+                    }
+
+                    if (col.textContent) {
+                        currentRow.push(
+                            quoteOpenChar + col.textContent.trim() + quoteCloseChar,
+                        );
+                    }
+                }
+
+                if (currentRow.length > 0) {
+                    table.push(currentRow);
+                }
+                currentRow = [];
+            });
+
+            if (table.length > 0) {
+                const text = table.map((row) => row.join(delimiterChar)).join(rowBreak);
+                if (getWindow().navigator.clipboard && text) {
+                    getWindow()
+                        .navigator.clipboard.writeText(text)
+                        .then(
+                            () => {
+                            },
+                            (err) => {
+                            },
+                        );
+                }
+            }
+        });
+    }
+
+    const selectRowCallback = (event) => {
+        const element = findTargetElementFromEvent(
+            event,
+            "data-monster-role",
+            "select-row",
+        );
+        if (element) {
+            const key = element.parentNode.getAttribute(
+                "data-monster-insert-reference",
+            );
+            const row = self.getGridElements(
+                `[data-monster-insert-reference="${key}"]`,
+            );
+
+            const index = key.split("-").pop();
+
+            if (element.checked) {
+                row.forEach((col) => {
+                    col.classList.add("selected");
+                });
+
+                fireCustomEvent(self, "monster-datatable-row-selected", {
+                    index: index,
+                });
+            } else {
+                row.forEach((col) => {
+                    col.classList.remove("selected");
+                });
+
+                fireCustomEvent(self, "monster-datatable-row-deselected", {
+                    index: index,
+                });
+            }
+
+            fireCustomEvent(this, "monster-datatable-selection-changed", {});
+        }
+
+        const rows = self.getGridElements(`[data-monster-role="select-row"]`);
+        const allSelected = Array.from(rows).every((row) => row.checked);
+        const selectAll = this[gridHeadersElementSymbol].querySelector(
+            `[data-monster-role="select-all"]`,
+        );
+        selectAll.checked = allSelected;
+    };
+
+    this[gridElementSymbol].addEventListener("click", selectRowCallback);
+    this[gridElementSymbol].addEventListener("touch", selectRowCallback);
+
+    const selectAllCallback = (event) => {
+        const element = findTargetElementFromEvent(
+            event,
+            "data-monster-role",
+            "select-all",
+        );
+        if (element) {
+            const mode = element.checked;
+
+            const rows = this.getGridElements(`[data-monster-role="select-row"]`);
+            rows.forEach((row) => {
+                row.checked = mode;
+            });
+
+            if (mode) {
+                fireCustomEvent(this, "monster-datatable-all-rows-selected", {});
+            } else {
+                fireCustomEvent(this, "monster-datatable-all-rows-deselected", {});
+            }
+
+            fireCustomEvent(this, "monster-datatable-selection-changed", {});
+        }
+    };
+
+    this[gridHeadersElementSymbol].addEventListener("click", selectAllCallback);
+    this[gridHeadersElementSymbol].addEventListener("touch", selectAllCallback);
 }
 
 /**
  * @private
  */
 function initGridAndStructs(hostConfig, headerOrderMap) {
-	const rowID = this.getOption("templateMapping.row-key");
-
-	if (!this[gridElementSymbol]) {
-		throw new Error("no grid element is defined");
-	}
-
-	let template;
-	getSlottedElements.call(this).forEach((e) => {
-		if (e instanceof HTMLTemplateElement && e.id === rowID) {
-			template = e;
-		}
-	});
-
-	if (!template) {
-		throw new Error("no template is defined");
-	}
-
-	const rowCount = template.content.children.length;
-
-	const headers = [];
-
-	for (let i = 0; i < rowCount; i++) {
-		let hClass = "";
-		const row = template.content.children[i];
-
-		let mode = "";
-		if (row.hasAttribute(ATTRIBUTE_DATATABLE_MODE)) {
-			mode = row.getAttribute(ATTRIBUTE_DATATABLE_MODE);
-		}
-
-		let grid = row.getAttribute(ATTRIBUTE_DATATABLE_GRID_TEMPLATE);
-		if (!grid || grid === "" || grid === "auto") {
-			grid = "minmax(0, 1fr)";
-		}
-
-		let label = "";
-		let labelKey = "";
-
-		if (row.hasAttribute(ATTRIBUTE_DATATABLE_HEAD)) {
-			label = row.getAttribute(ATTRIBUTE_DATATABLE_HEAD);
-			labelKey = label;
-
-			try {
-				if (label.startsWith("i18n:")) {
-					label = label.substring(5, label.length);
-					label = getDocumentTranslations().getText(label, label);
-				}
-			} catch (e) {
-				label = "i18n error " + label;
-			}
-		}
-
-		if (!label) {
-			label = i + 1 + "";
-			mode = ATTRIBUTE_DATATABLE_MODE_FIXED;
-			labelKey = label;
-		}
-
-		if (isObject(hostConfig) && hostConfig.hasOwnProperty(label)) {
-			if (hostConfig[label] === false) {
-				mode = ATTRIBUTE_DATATABLE_MODE_HIDDEN;
-			} else {
-				mode = ATTRIBUTE_DATATABLE_MODE_VISIBLE;
-			}
-		}
-
-		let align = "";
-		if (row.hasAttribute(ATTRIBUTE_DATATABLE_ALIGN)) {
-			align = row.getAttribute(ATTRIBUTE_DATATABLE_ALIGN);
-		}
-
-		switch (align) {
-			case "center":
-				hClass = "flex-center";
-				break;
-			case "end":
-				hClass = "flex-end";
-				break;
-			case "start":
-				hClass = "flex-start";
-				break;
-			default:
-				hClass = "flex-start";
-		}
-
-		let field = "";
-		let direction = DIRECTION_NONE;
-		if (row.hasAttribute(ATTRIBUTE_DATATABLE_SORTABLE)) {
-			field = row.getAttribute(ATTRIBUTE_DATATABLE_SORTABLE).trim();
-			const parts = field.split(" ").map((item) => item.trim());
-			field = parts[0];
-
-			if (headerOrderMap.has(field)) {
-				direction = headerOrderMap.get(field);
-			} else if (
-				parts.length === 2 &&
-				[DIRECTION_ASC, DIRECTION_DESC].indexOf(parts[1]) !== -1
-			) {
-				direction = parts[1];
-			}
-		}
-
-		if (mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
-			hClass += " hidden";
-		}
-
-		const features = [];
-		if (row.hasAttribute(ATTRIBUTE_DATATABLE_FEATURES)) {
-			const features = row
-				.getAttribute(ATTRIBUTE_DATATABLE_FEATURES)
-				.split(" ");
-			features.forEach((feature) => {
-				features.push(feature.trim());
-
-				if (feature === "select") {
-					label = "<input type='checkbox' data-monster-role='select-all' />";
-
-					while (row.firstChild) {
-						row.removeChild(row.firstChild);
-					}
-
-					const checkbox = document.createElement("input");
-					checkbox.type = "checkbox";
-					checkbox.setAttribute("data-monster-role", "select-row");
-					row.appendChild(checkbox);
-				}
-			});
-		}
-
-		const header = new Header();
-		header.setInternals({
-			field: field,
-			label: label,
-			classes: hClass,
-			index: i,
-			mode: mode,
-			grid: grid,
-			labelKey: labelKey,
-			direction: direction,
-			features: features,
-		});
-
-		headers.push(header);
-	}
-
-	this.setOption("headers", headers);
-	queueMicrotask(() => {
-		storeOrderStatement.call(this, this.getOption("features.autoInit"));
-	});
+    const rowID = this.getOption("templateMapping.row-key");
+
+    if (!this[gridElementSymbol]) {
+        addErrorAttribute(this, "no grid element found");
+        return;
+    }
+
+    let template;
+    getSlottedElements.call(this).forEach((e) => {
+        if (e instanceof HTMLTemplateElement && e.id === rowID) {
+            template = e;
+        }
+    });
+
+    if (!template) {
+        addErrorAttribute(
+            this,
+            "no template found, please add a template",
+        );
+        return;
+    }
+
+    const rowCount = template.content.children.length;
+
+    const headers = [];
+
+    for (let i = 0; i < rowCount; i++) {
+        let hClass = "";
+        const row = template.content.children[i];
+
+        let mode = "";
+        if (row.hasAttribute(ATTRIBUTE_DATATABLE_MODE)) {
+            mode = row.getAttribute(ATTRIBUTE_DATATABLE_MODE);
+        }
+
+        let grid = row.getAttribute(ATTRIBUTE_DATATABLE_GRID_TEMPLATE);
+        if (!grid || grid === "" || grid === "auto") {
+            grid = "minmax(0, 1fr)";
+        }
+
+        let label = "";
+        let labelKey = "";
+
+        if (row.hasAttribute(ATTRIBUTE_DATATABLE_HEAD)) {
+            label = row.getAttribute(ATTRIBUTE_DATATABLE_HEAD);
+            labelKey = label;
+
+            try {
+                if (label.startsWith("i18n:")) {
+                    label = label.substring(5, label.length);
+                    label = getDocumentTranslations().getText(label, label);
+                }
+            } catch (e) {
+                label = "i18n error " + label;
+            }
+        }
+
+        if (!label) {
+            label = i + 1 + "";
+            mode = ATTRIBUTE_DATATABLE_MODE_FIXED;
+            labelKey = label;
+        }
+
+        if (isObject(hostConfig) && hostConfig.hasOwnProperty(label)) {
+            if (hostConfig[label] === false) {
+                mode = ATTRIBUTE_DATATABLE_MODE_HIDDEN;
+            } else {
+                mode = ATTRIBUTE_DATATABLE_MODE_VISIBLE;
+            }
+        }
+
+        let align = "";
+        if (row.hasAttribute(ATTRIBUTE_DATATABLE_ALIGN)) {
+            align = row.getAttribute(ATTRIBUTE_DATATABLE_ALIGN);
+        }
+
+        switch (align) {
+            case "center":
+                hClass = "flex-center";
+                break;
+            case "end":
+                hClass = "flex-end";
+                break;
+            case "start":
+                hClass = "flex-start";
+                break;
+            default:
+                hClass = "flex-start";
+        }
+
+        let field = "";
+        let direction = DIRECTION_NONE;
+        if (row.hasAttribute(ATTRIBUTE_DATATABLE_SORTABLE)) {
+            field = row.getAttribute(ATTRIBUTE_DATATABLE_SORTABLE).trim();
+            const parts = field.split(" ").map((item) => item.trim());
+            field = parts[0];
+
+            if (headerOrderMap.has(field)) {
+                direction = headerOrderMap.get(field);
+            } else if (
+                parts.length === 2 &&
+                [DIRECTION_ASC, DIRECTION_DESC].indexOf(parts[1]) !== -1
+            ) {
+                direction = parts[1];
+            }
+        }
+
+        if (mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
+            hClass += " hidden";
+        }
+
+        const features = [];
+
+        if (row.hasAttribute(ATTRIBUTE_DATATABLE_FEATURES)) {
+            const fl = row
+                .getAttribute(ATTRIBUTE_DATATABLE_FEATURES)
+                .split(" ");
+
+            fl.forEach((feature) => {
+                features.push(feature.trim());
+
+                if (feature === "select") {
+                    label = "<input type='checkbox' data-monster-role='select-all' />";
+
+                    while (row.firstChild) {
+                        row.removeChild(row.firstChild);
+                    }
+
+                    const checkbox = document.createElement("input");
+                    checkbox.type = "checkbox";
+                    checkbox.setAttribute("data-monster-role", "select-row");
+                    row.appendChild(checkbox);
+                }
+            });
+        }
+
+        const header = new Header();
+        header.setInternals({
+            field: field,
+            label: label,
+            classes: hClass,
+            index: i,
+            mode: mode,
+            grid: grid,
+            labelKey: labelKey,
+            direction: direction,
+            features: features,
+        });
+
+        headers.push(header);
+    }
+
+    this.setOption("headers", headers);
+    queueMicrotask(() => {
+        storeOrderStatement.call(this, this.getOption("features.autoInit"));
+    });
 }
 
 /**
@@ -1106,101 +1118,101 @@ function initGridAndStructs(hostConfig, headerOrderMap) {
  * @returns {object}
  */
 function getTranslations() {
-	const locale = getLocaleOfDocument();
-	switch (locale.language) {
-		case "de":
-			return {
-				theListContainsNoEntries: "Die Liste enthält keine Einträge",
-				copyAll: "Alles kopieren",
-				helpText:
-					"<p>Sie können die Werte aus einzelnen Zeilen<br>" +
-					"in die Zwischenablage kopieren, indem Sie auf die entsprechende Spalte doppelklicken.</p>" +
-					"<p>Um eine ganze Zeile zu kopieren, halten Sie die Umschalttaste gedrückt, während Sie klicken.<br>" +
-					"Wenn Sie alle Zeilen kopieren möchten, können Sie die Schaltfläche <strong>Alles kopieren</strong> verwenden.</p>",
-			};
-		case "fr":
-			return {
-				theListContainsNoEntries: "La liste ne contient aucune entrée",
-				copyAll: "Copier tout",
-				helpText:
-					"<p>Vous pouvez copier les valeurs des rangées individuelles<br>" +
-					"dans le presse-papiers en double-cliquant sur la colonne concernée.</p>" +
-					"<p>Pour copier une rangée entière, maintenez la touche Maj enfoncée tout en cliquant.<br>" +
-					"Si vous souhaitez copier toutes les rangées, vous pouvez utiliser le bouton <strong>Copier tout</strong>.</p>",
-			};
-		case "sp":
-			return {
-				theListContainsNoEntries: "La lista no contiene entradas",
-				copyAll: "Copiar todo",
-				helpText:
-					"<p>Puedes copiar los valores de filas individuales<br>" +
-					"al portapapeles haciendo doble clic en la columna correspondiente.</p>" +
-					"<p>Para copiar una fila entera, mantén presionada la tecla Shift mientras haces clic.<br>" +
-					"Si quieres copiar todas las filas, puedes usar el botón <strong>Copiar todo</strong>.</p>",
-			};
-		case "it":
-			return {
-				theListContainsNoEntries: "L'elenco non contiene voci",
-				copyAll: "Copia tutto",
-				helpText:
-					"<p>Puoi copiare i valori dalle singole righe<br>" +
-					"negli appunti facendo doppio clic sulla colonna relativa.</p>" +
-					"<p>Per copiare un'intera riga, tieni premuto il tasto Shift mentre clicchi.<br>" +
-					"Se vuoi copiare tutte le righe, puoi usare il pulsante <strong>Copia tutto</strong>.</p>",
-			};
-		case "pl":
-			return {
-				theListContainsNoEntries: "Lista nie zawiera wpisów",
-				copyAll: "Kopiuj wszystko",
-				helpText:
-					"<p>Możesz skopiować wartości z poszczególnych wierszy<br>" +
-					"do schowka, klikając dwukrotnie na odpowiednią kolumnę.</p>" +
-					"<p>Aby skopiować cały wiersz, przytrzymaj klawisz Shift podczas klikania.<br>" +
-					"Jeśli chcesz skopiować wszystkie wiersze, możesz użyć przycisku <strong>Kopiuj wszystko</strong>.</p>",
-			};
-		case "no":
-			return {
-				theListContainsNoEntries: "Listen inneholder ingen oppføringer",
-				copyAll: "Kopier alt",
-				helpText:
-					"<p>Du kan kopiere verdier fra enkeltrader<br>" +
-					"til utklippstavlen ved å dobbeltklikke på den relevante kolonnen.</p>" +
-					"<p>For å kopiere en hel rad, hold nede Skift-tasten mens du klikker.<br>" +
-					"Hvis du vil kopiere alle radene, kan du bruke knappen <strong>Kopier alt</strong>.</p>",
-			};
-		case "dk":
-			return {
-				theListContainsNoEntries: "Listen indeholder ingen poster",
-				copyAll: "Kopiér alt",
-				helpText:
-					"<p>Du kan kopiere værdier fra enkelte rækker<br>" +
-					"til udklipsholderen ved at dobbeltklikke på den relevante kolonne.</p>" +
-					"<p>For at kopiere en hel række, hold Shift-tasten nede, mens du klikker.<br>" +
-					"Hvis du vil kopiere alle rækker, kan du bruge knappen <strong>Kopiér alt</strong>.</p>",
-			};
-		case "sw":
-			return {
-				theListContainsNoEntries: "Listan innehåller inga poster",
-				copyAll: "Kopiera allt",
-				helpText:
-					"<p>Du kan kopiera värden från enskilda rader<br>" +
-					"till urklipp genom att dubbelklicka på den relevanta kolumnen.</p>" +
-					"<p>För att kopiera en hel rad, håll ned Shift-tangenten medan du klickar.<br>" +
-					"Om du vill kopiera alla rader kan du använda knappen <strong>Kopiera allt</strong>.</p>",
-			};
-
-		case "en":
-		default:
-			return {
-				theListContainsNoEntries: "The list contains no entries",
-				copyAll: "Copy all",
-				helpText:
-					"<p>You can copy the values from individual rows<br>" +
-					"to the clipboard by double-clicking on the relevant column.</p>" +
-					"<p>To copy an entire row, hold down the Shift key while clicking.<br>" +
-					"If you want to copy all rows, you can use the <strong>Copy All</strong> button.</p>",
-			};
-	}
+    const locale = getLocaleOfDocument();
+    switch (locale.language) {
+        case "de":
+            return {
+                theListContainsNoEntries: "Die Liste enthält keine Einträge",
+                copyAll: "Alles kopieren",
+                helpText:
+                    "<p>Sie können die Werte aus einzelnen Zeilen<br>" +
+                    "in die Zwischenablage kopieren, indem Sie auf die entsprechende Spalte doppelklicken.</p>" +
+                    "<p>Um eine ganze Zeile zu kopieren, halten Sie die Umschalttaste gedrückt, während Sie klicken.<br>" +
+                    "Wenn Sie alle Zeilen kopieren möchten, können Sie die Schaltfläche <strong>Alles kopieren</strong> verwenden.</p>",
+            };
+        case "fr":
+            return {
+                theListContainsNoEntries: "La liste ne contient aucune entrée",
+                copyAll: "Copier tout",
+                helpText:
+                    "<p>Vous pouvez copier les valeurs des rangées individuelles<br>" +
+                    "dans le presse-papiers en double-cliquant sur la colonne concernée.</p>" +
+                    "<p>Pour copier une rangée entière, maintenez la touche Maj enfoncée tout en cliquant.<br>" +
+                    "Si vous souhaitez copier toutes les rangées, vous pouvez utiliser le bouton <strong>Copier tout</strong>.</p>",
+            };
+        case "sp":
+            return {
+                theListContainsNoEntries: "La lista no contiene entradas",
+                copyAll: "Copiar todo",
+                helpText:
+                    "<p>Puedes copiar los valores de filas individuales<br>" +
+                    "al portapapeles haciendo doble clic en la columna correspondiente.</p>" +
+                    "<p>Para copiar una fila entera, mantén presionada la tecla Shift mientras haces clic.<br>" +
+                    "Si quieres copiar todas las filas, puedes usar el botón <strong>Copiar todo</strong>.</p>",
+            };
+        case "it":
+            return {
+                theListContainsNoEntries: "L'elenco non contiene voci",
+                copyAll: "Copia tutto",
+                helpText:
+                    "<p>Puoi copiare i valori dalle singole righe<br>" +
+                    "negli appunti facendo doppio clic sulla colonna relativa.</p>" +
+                    "<p>Per copiare un'intera riga, tieni premuto il tasto Shift mentre clicchi.<br>" +
+                    "Se vuoi copiare tutte le righe, puoi usare il pulsante <strong>Copia tutto</strong>.</p>",
+            };
+        case "pl":
+            return {
+                theListContainsNoEntries: "Lista nie zawiera wpisów",
+                copyAll: "Kopiuj wszystko",
+                helpText:
+                    "<p>Możesz skopiować wartości z poszczególnych wierszy<br>" +
+                    "do schowka, klikając dwukrotnie na odpowiednią kolumnę.</p>" +
+                    "<p>Aby skopiować cały wiersz, przytrzymaj klawisz Shift podczas klikania.<br>" +
+                    "Jeśli chcesz skopiować wszystkie wiersze, możesz użyć przycisku <strong>Kopiuj wszystko</strong>.</p>",
+            };
+        case "no":
+            return {
+                theListContainsNoEntries: "Listen inneholder ingen oppføringer",
+                copyAll: "Kopier alt",
+                helpText:
+                    "<p>Du kan kopiere verdier fra enkeltrader<br>" +
+                    "til utklippstavlen ved å dobbeltklikke på den relevante kolonnen.</p>" +
+                    "<p>For å kopiere en hel rad, hold nede Skift-tasten mens du klikker.<br>" +
+                    "Hvis du vil kopiere alle radene, kan du bruke knappen <strong>Kopier alt</strong>.</p>",
+            };
+        case "dk":
+            return {
+                theListContainsNoEntries: "Listen indeholder ingen poster",
+                copyAll: "Kopiér alt",
+                helpText:
+                    "<p>Du kan kopiere værdier fra enkelte rækker<br>" +
+                    "til udklipsholderen ved at dobbeltklikke på den relevante kolonne.</p>" +
+                    "<p>For at kopiere en hel række, hold Shift-tasten nede, mens du klikker.<br>" +
+                    "Hvis du vil kopiere alle rækker, kan du bruge knappen <strong>Kopiér alt</strong>.</p>",
+            };
+        case "sw":
+            return {
+                theListContainsNoEntries: "Listan innehåller inga poster",
+                copyAll: "Kopiera allt",
+                helpText:
+                    "<p>Du kan kopiera värden från enskilda rader<br>" +
+                    "till urklipp genom att dubbelklicka på den relevanta kolumnen.</p>" +
+                    "<p>För att kopiera en hel rad, håll ned Shift-tangenten medan du klickar.<br>" +
+                    "Om du vill kopiera alla rader kan du använda knappen <strong>Kopiera allt</strong>.</p>",
+            };
+
+        case "en":
+        default:
+            return {
+                theListContainsNoEntries: "The list contains no entries",
+                copyAll: "Copy all",
+                helpText:
+                    "<p>You can copy the values from individual rows<br>" +
+                    "to the clipboard by double-clicking on the relevant column.</p>" +
+                    "<p>To copy an entire row, hold down the Shift key while clicking.<br>" +
+                    "If you want to copy all rows, you can use the <strong>Copy All</strong> button.</p>",
+            };
+    }
 }
 
 /**
@@ -1208,79 +1220,79 @@ function getTranslations() {
  * @return {string}
  */
 export function getStoredOrderConfigKey() {
-	return generateUniqueConfigKey("datatable", this?.id, "stored-order");
+    return generateUniqueConfigKey("datatable", this?.id, "stored-order");
 }
 
 /**
  * @private
  */
 function storeOrderStatement(doFetch) {
-	const headers = this.getOption("headers");
-	const statement = createOrderStatement(headers);
-	setDataSource.call(this, { orderBy: statement }, doFetch);
+    const headers = this.getOption("headers");
+    const statement = createOrderStatement(headers);
+    setDataSource.call(this, {orderBy: statement}, doFetch);
 
-	const host = findElementWithSelectorUpwards(this, "monster-host");
-	if (!(host && this.id)) {
-		return;
-	}
+    const host = findElementWithSelectorUpwards(this, "monster-host");
+    if (!(host && this.id)) {
+        return;
+    }
 
-	const configKey = getStoredOrderConfigKey.call(this);
+    const configKey = getStoredOrderConfigKey.call(this);
 
-	// statement explode with , and remove all empty
-	const list = statement.split(",").filter((item) => item.trim() !== "");
-	if (list.length === 0) {
-		return;
-	}
+    // statement explode with , and remove all empty
+    const list = statement.split(",").filter((item) => item.trim() !== "");
+    if (list.length === 0) {
+        return;
+    }
 
-	host.setConfig(configKey, list);
+    host.setConfig(configKey, list);
 }
 
 /**
  * @private
  */
 function updateGrid() {
-	if (!this[gridElementSymbol]) {
-		throw new Error("no grid element is defined");
-	}
-
-	let gridTemplateColumns = "";
-
-	const headers = this.getOption("headers");
-
-	let styles = "";
-
-	for (let i = 0; i < headers.length; i++) {
-		const header = headers[i];
-
-		if (header.mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
-			styles += `[data-monster-role=datatable]>[data-monster-head="${header.labelKey}"] { display: none; }\n`;
-			styles += `[data-monster-role=datatable-headers]>[data-monster-index="${header.index}"] { display: none; }\n`;
-		} else {
-			gridTemplateColumns += `${header.grid} `;
-		}
-	}
-
-	const sheet = new CSSStyleSheet();
-	if (styles !== "") sheet.replaceSync(styles);
-	this.shadowRoot.adoptedStyleSheets = [...DataTable.getCSSStyleSheet(), sheet];
-
-	const bodyWidth = this.parentNode.clientWidth;
-
-	const breakpoint = this.getOption("responsive.breakpoint");
-	this[dataControlElementSymbol].classList.toggle(
-		"small",
-		bodyWidth <= breakpoint,
-	);
-
-	if (bodyWidth > breakpoint) {
-		this[gridElementSymbol].style.gridTemplateColumns =
-			`${gridTemplateColumns}`;
-		this[gridHeadersElementSymbol].style.gridTemplateColumns =
-			`${gridTemplateColumns}`;
-	} else {
-		this[gridElementSymbol].style.gridTemplateColumns = "auto";
-		this[gridHeadersElementSymbol].style.gridTemplateColumns = "auto";
-	}
+    if (!this[gridElementSymbol]) {
+        throw new Error("no grid element is defined");
+    }
+
+    let gridTemplateColumns = "";
+
+    const headers = this.getOption("headers");
+
+    let styles = "";
+
+    for (let i = 0; i < headers.length; i++) {
+        const header = headers[i];
+
+        if (header.mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) {
+            styles += `[data-monster-role=datatable]>[data-monster-head="${header.labelKey}"] { display: none; }\n`;
+            styles += `[data-monster-role=datatable-headers]>[data-monster-index="${header.index}"] { display: none; }\n`;
+        } else {
+            gridTemplateColumns += `${header.grid} `;
+        }
+    }
+
+    const sheet = new CSSStyleSheet();
+    if (styles !== "") sheet.replaceSync(styles);
+    this.shadowRoot.adoptedStyleSheets = [...DataTable.getCSSStyleSheet(), sheet];
+
+    const bodyWidth = this.parentNode.clientWidth;
+
+    const breakpoint = this.getOption("responsive.breakpoint");
+    this[dataControlElementSymbol].classList.toggle(
+        "small",
+        bodyWidth <= breakpoint,
+    );
+
+    if (bodyWidth > breakpoint) {
+        this[gridElementSymbol].style.gridTemplateColumns =
+            `${gridTemplateColumns}`;
+        this[gridHeadersElementSymbol].style.gridTemplateColumns =
+            `${gridTemplateColumns}`;
+    } else {
+        this[gridElementSymbol].style.gridTemplateColumns = "auto";
+        this[gridHeadersElementSymbol].style.gridTemplateColumns = "auto";
+    }
 }
 
 /**
@@ -1288,20 +1300,20 @@ function updateGrid() {
  * @param {Header[]} headers
  * @param {bool} doFetch
  */
-function setDataSource({ orderBy }, doFetch) {
-	const datasource = this[datasourceLinkedElementSymbol];
+function setDataSource({orderBy}, doFetch) {
+    const datasource = this[datasourceLinkedElementSymbol];
 
-	if (!datasource) {
-		return;
-	}
+    if (!datasource) {
+        return;
+    }
 
-	if (isFunction(datasource?.setParameters)) {
-		datasource.setParameters({ orderBy });
-	}
+    if (isFunction(datasource?.setParameters)) {
+        datasource.setParameters({orderBy});
+    }
 
-	if (doFetch !== false && isFunction(datasource?.fetch)) {
-		datasource.fetch();
-	}
+    if (doFetch !== false && isFunction(datasource?.fetch)) {
+        datasource.fetch();
+    }
 }
 
 /**
@@ -1309,30 +1321,30 @@ function setDataSource({ orderBy }, doFetch) {
  * @return {DataTable}
  */
 function initControlReferences() {
-	if (!this.shadowRoot) {
-		throw new Error("no shadow-root is defined");
-	}
+    if (!this.shadowRoot) {
+        throw new Error("no shadow-root is defined");
+    }
 
-	this[dataControlElementSymbol] = this.shadowRoot.querySelector(
-		"[data-monster-role=control]",
-	);
+    this[dataControlElementSymbol] = this.shadowRoot.querySelector(
+        "[data-monster-role=control]",
+    );
 
-	this[gridElementSymbol] = this.shadowRoot.querySelector(
-		"[data-monster-role=datatable]",
-	);
+    this[gridElementSymbol] = this.shadowRoot.querySelector(
+        "[data-monster-role=datatable]",
+    );
 
-	this[gridHeadersElementSymbol] = this.shadowRoot.querySelector(
-		"[data-monster-role=datatable-headers]",
-	);
+    this[gridHeadersElementSymbol] = this.shadowRoot.querySelector(
+        "[data-monster-role=datatable-headers]",
+    );
 
-	this[columnBarElementSymbol] =
-		this.shadowRoot.querySelector("monster-column-bar");
+    this[columnBarElementSymbol] =
+        this.shadowRoot.querySelector("monster-column-bar");
 
-	this[copyAllElementSymbol] = this.shadowRoot.querySelector(
-		"[data-monster-role=copy-all]",
-	);
+    this[copyAllElementSymbol] = this.shadowRoot.querySelector(
+        "[data-monster-role=copy-all]",
+    );
 
-	return this;
+    return this;
 }
 
 /**
@@ -1342,22 +1354,22 @@ function initControlReferences() {
  * @throws {Error} the datasource could not be initialized
  */
 function initOptionsFromArguments() {
-	const options = {};
-	const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR);
-
-	if (selector) {
-		options.datasource = { selector: selector };
-	}
-
-	const breakpoint = this.getAttribute(
-		ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
-	);
-	if (breakpoint) {
-		options.responsive = {};
-		options.responsive.breakpoint = parseInt(breakpoint);
-	}
-
-	return options;
+    const options = {};
+    const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR);
+
+    if (selector) {
+        options.datasource = {selector: selector};
+    }
+
+    const breakpoint = this.getAttribute(
+        ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT,
+    );
+    if (breakpoint) {
+        options.responsive = {};
+        options.responsive.breakpoint = parseInt(breakpoint);
+    }
+
+    return options;
 }
 
 /**
@@ -1365,7 +1377,7 @@ function initOptionsFromArguments() {
  * @return {string}
  */
 function getEmptyTemplate() {
-	return `<monster-state data-monster-role="empty-without-action">
+    return `<monster-state data-monster-role="empty-without-action">
     <div part="visual">
         <svg width="4rem" height="4rem" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
             <path d="m21.5 22h-19c-1.378 0-2.5-1.121-2.5-2.5v-7c0-.07.015-.141.044-.205l3.969-8.82c.404-.896 1.299-1.475 2.28-1.475h11.414c.981 0 1.876.579 2.28 1.475l3.969 8.82c.029.064.044.135.044.205v7c0 1.379-1.122 2.5-2.5 2.5zm-20.5-9.393v6.893c0 .827.673 1.5 1.5 1.5h19c.827 0 1.5-.673 1.5-1.5v-6.893l-3.925-8.723c-.242-.536-.779-.884-1.368-.884h-11.414c-.589 0-1.126.348-1.368.885z"/>
@@ -1383,8 +1395,8 @@ function getEmptyTemplate() {
  * @return {string}
  */
 function getTemplate() {
-	// language=HTML
-	return `
+    // language=HTML
+    return `
         <div data-monster-role="control" part="control" data-monster-attributes="class path:classes.control">
             <template id="headers-row">
                 <div data-monster-attributes="class path:headers-row.classes,
diff --git a/source/components/form/action-button.mjs b/source/components/form/action-button.mjs
index 816a00da6e3f4c79d9fd505162e2a8a5050f0be0..e5b567a728a56984c6828393b4c09dc1add110a3 100644
--- a/source/components/form/action-button.mjs
+++ b/source/components/form/action-button.mjs
@@ -31,6 +31,7 @@ import {
 	ATTRIBUTE_ERRORMESSAGE,
 	ATTRIBUTE_ROLE,
 } from "../../dom/constants.mjs";
+import {addErrorAttribute} from "../../dom/error.mjs";
 
 export { ActionButton };
 
@@ -163,7 +164,7 @@ function initEventHandler() {
 				try {
 					updateButtonsI18n.call(this);
 				} catch (e) {
-					addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
+					addErrorAttribute(this, e.message);
 				}
 
 				memButtons = JSON.stringify(this.getOption("buttons"));
diff --git a/source/components/form/api-button.mjs b/source/components/form/api-button.mjs
index 62c48f1c5ddcdf48c29f6d3d209e46484110a4a0..8b7183e1292f35cdb7ae031cfe651d6e872c1805 100644
--- a/source/components/form/api-button.mjs
+++ b/source/components/form/api-button.mjs
@@ -35,7 +35,7 @@ import { ApiButtonStyleSheet } from "./stylesheet/api-button.mjs";
 import { isObject, isFunction } from "../../types/is.mjs";
 import { getGlobal } from "../../types/global.mjs";
 import { Formatter } from "../../text/formatter.mjs";
-
+import {addErrorAttribute} from "../../dom/error.mjs";
 export { ApiButton };
 
 /**
@@ -189,12 +189,12 @@ class ApiButton extends ActionButton {
 		let flag = false;
 		let apiEqualUrl = false;
 		if (labelTemplate === "") {
-			addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "empty label template");
+			addErrorAttribute(this, "empty label template");
 			flag = true;
 		}
 
 		if (apiTemplate === "") {
-			addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "empty api template");
+			addErrorAttribute(this, "empty api template");
 			flag = true;
 		}
 
@@ -242,7 +242,7 @@ class ApiButton extends ActionButton {
 		try {
 			this.updateI18n();
 		} catch (e) {
-			addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
+			addErrorAttribute(this, e.message);
 		}
 
 		this.setOption("buttons", buttons);
diff --git a/source/components/form/button-bar.mjs b/source/components/form/button-bar.mjs
index ab2791c9fd9a0ae2be82a49b51c3793488904251..76db511bd406b74bed6828813a80adddc5b7cbf5 100644
--- a/source/components/form/button-bar.mjs
+++ b/source/components/form/button-bar.mjs
@@ -41,7 +41,7 @@ import { STYLE_DISPLAY_MODE_BLOCK } from "./constants.mjs";
 import { ButtonBarStyleSheet } from "./stylesheet/button-bar.mjs";
 import { positionPopper } from "./util/floating-ui.mjs";
 import { convertToPixels } from "../../dom/dimension.mjs";
-
+import {addErrorAttribute} from "../../dom/error.mjs";
 export { ButtonBar };
 
 /**
@@ -358,9 +358,8 @@ function initEventHandler() {
 				try {
 					checkAndRearrangeButtons.call(self);
 				} catch (error) {
-					addAttributeToken(
+					addErrorAttribute(
 						this,
-						ATTRIBUTE_ERRORMESSAGE,
 						error?.message || "An error occurred while rearranging the buttons",
 					);
 				}
diff --git a/source/components/form/button.mjs b/source/components/form/button.mjs
index 3fa0957e129f1650b595405627286fb71e407a81..f99c16c0299de90b7bc26856d7cc615ed72ed5b9 100644
--- a/source/components/form/button.mjs
+++ b/source/components/form/button.mjs
@@ -30,6 +30,7 @@ import { ATTRIBUTE_BUTTON_CLASS } from "./constants.mjs";
 import { ButtonStyleSheet } from "./stylesheet/button.mjs";
 import { RippleStyleSheet } from "../stylesheet/ripple.mjs";
 import { fireCustomEvent } from "../../dom/events.mjs";
+import {addErrorAttribute} from "../../dom/error.mjs";
 
 export { Button };
 
diff --git a/source/components/form/context-error.mjs b/source/components/form/context-error.mjs
index a79b9831943105e535b29b05b6a65d76532f63e6..1885544e54eea7b71d64061a0a7d40faf6f4fca3 100644
--- a/source/components/form/context-error.mjs
+++ b/source/components/form/context-error.mjs
@@ -24,6 +24,7 @@ import {
 	ATTRIBUTE_ERRORMESSAGE,
 	ATTRIBUTE_ROLE,
 } from "../../dom/constants.mjs";
+import {addErrorAttribute} from "../../dom/error.mjs";
 
 export { ContextError };
 
diff --git a/source/components/form/field-set.mjs b/source/components/form/field-set.mjs
index cb6af5decb488b78df8fa22f60ad18b667c8ac88..b689981e27ba653ab253d3fbfa906e00cba358eb 100644
--- a/source/components/form/field-set.mjs
+++ b/source/components/form/field-set.mjs
@@ -29,6 +29,7 @@ import { FieldSetStyleSheet } from "./stylesheet/field-set.mjs";
 import "../layout/collapse.mjs";
 import "./toggle-switch.mjs";
 import { getLocaleOfDocument } from "../../dom/locale.mjs";
+import {addErrorAttribute} from "../../dom/error.mjs";
 
 export { FieldSet };
 
diff --git a/source/components/form/popper-button.mjs b/source/components/form/popper-button.mjs
index fba825ea0aa143d9e6aa3a34b3d126a6332baa1c..a6c1b25e7b85614832e08644f1418c1d08c585de 100644
--- a/source/components/form/popper-button.mjs
+++ b/source/components/form/popper-button.mjs
@@ -12,26 +12,27 @@
  * SPDX-License-Identifier: AGPL-3.0
  */
 
-import { instanceSymbol } from "../../constants.mjs";
-import { addAttributeToken } from "../../dom/attributes.mjs";
+import {instanceSymbol} from "../../constants.mjs";
+import {addAttributeToken} from "../../dom/attributes.mjs";
 import {
-	ATTRIBUTE_ERRORMESSAGE,
-	ATTRIBUTE_ROLE,
+    ATTRIBUTE_ERRORMESSAGE,
+    ATTRIBUTE_ROLE,
 } from "../../dom/constants.mjs";
 import {
-	assembleMethodSymbol,
-	registerCustomElement,
+    assembleMethodSymbol,
+    registerCustomElement,
 } from "../../dom/customelement.mjs";
-import { getDocument } from "../../dom/util.mjs";
-import { isFunction } from "../../types/is.mjs";
-import { DeadMansSwitch } from "../../util/deadmansswitch.mjs";
-import { Popper } from "./popper.mjs";
-import { STYLE_DISPLAY_MODE_BLOCK } from "./constants.mjs";
-import { PopperButtonStyleSheet } from "./stylesheet/popper-button.mjs";
-import { positionPopper } from "./util/floating-ui.mjs";
+import {getDocument} from "../../dom/util.mjs";
+import {isFunction} from "../../types/is.mjs";
+import {DeadMansSwitch} from "../../util/deadmansswitch.mjs";
+import {Popper} from "./popper.mjs";
+import {STYLE_DISPLAY_MODE_BLOCK} from "./constants.mjs";
+import {PopperButtonStyleSheet} from "./stylesheet/popper-button.mjs";
+import {positionPopper} from "./util/floating-ui.mjs";
 import "./button.mjs";
+import {addErrorAttribute} from "../../dom/error.mjs";
 
-export { PopperButton };
+export {PopperButton};
 
 /**
  * @private
@@ -140,205 +141,205 @@ const arrowElementSymbol = Symbol("arrowElement");
  * @fires monster-changed
  */
 class PopperButton extends Popper {
-	/**
-	 * This method is called by the `instanceof` operator.
-	 * @return {symbol}
-	 * @since 2.1.0
-	 */
-	static get [instanceSymbol]() {
-		return Symbol.for(
-			"@schukai/monster/components/form/popper-button@@instance",
-		);
-	}
-
-	/**
-	 * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
-	 * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
-	 *
-	 * The individual configuration values can be found in the table.
-	 *
-	 * @property {Object} templates - The templates for the control.
-	 * @property {string} templates.main - The main template.
-	 * @property {object} labels - The labels for the control.
-	 * @property {string} labels.button - The label for the button.
-	 * @property {string} content - The content of the popper.
-	 * @property {object} popper - The popper options.
-	 * @extends {Button.defaults}
-	 */
-	get defaults() {
-		return Object.assign({}, super.defaults, {
-			templates: {
-				main: getTemplate(),
-			},
-			actions: {
-				click: (e) => {
-					this.toggleDialog();
-				},
-			},
-			classes: {
-				button: "monster-button",
-			},
-			labels: {
-				button: '<slot name="button"></slot>',
-			},
-			mode: "click",
-			value: null,
-		});
-	}
-
-	/**
-	 *
-	 * @return {Monster.Components.Form.PopperButton}
-	 */
-	[assembleMethodSymbol]() {
-		super[assembleMethodSymbol]();
-		initControlReferences.call(this);
-		initEventHandler.call(this);
-
-		return this;
-	}
-
-	/**
-	 * @return {string}
-	 */
-	static getTag() {
-		return "monster-popper-button";
-	}
-
-	/**
-	 * @return {CSSStyleSheet[]}
-	 */
-	static getCSSStyleSheet() {
-		const styles = super.getCSSStyleSheet();
-		styles.push(PopperButtonStyleSheet);
-		return styles;
-	}
-
-	/**
-	 * @return {void}
-	 */
-	connectedCallback() {
-		super.connectedCallback();
-
-		const document = getDocument();
-
-		for (const [, type] of Object.entries(["click", "touch"])) {
-			// close on outside ui-events
-			document.addEventListener(type, this[closeEventHandler]);
-		}
-
-		updatePopper.call(this);
-		attachResizeObserver.call(this);
-	}
-
-	/**
-	 * @return {void}
-	 */
-	disconnectedCallback() {
-		super.disconnectedCallback();
-
-		// close on outside ui-events
-		for (const [, type] of Object.entries(["click", "touch"])) {
-			document.removeEventListener(type, this[closeEventHandler]);
-		}
-
-		disconnectResizeObserver.call(this);
-	}
-
-	/**
-	 * The Button.click() method simulates a click on the internal button element.
-	 *
-	 * @since 3.27.0
-	 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click}
-	 */
-	click() {
-		if (this.getOption("disabled") === true) {
-			return;
-		}
-
-		if (
-			this[buttonElementSymbol] &&
-			isFunction(this[buttonElementSymbol].click)
-		) {
-			this[buttonElementSymbol].click();
-		}
-	}
-
-	/**
-	 * The Button.focus() method sets focus on the internal button element.
-	 *
-	 * @since 3.27.0
-	 * @param {Object} options
-	 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus}
-	 */
-	focus(options) {
-		if (this.getOption("disabled") === true) {
-			return;
-		}
-
-		if (
-			this[buttonElementSymbol] &&
-			isFunction(this[buttonElementSymbol].focus)
-		) {
-			this[buttonElementSymbol].focus(options);
-		}
-	}
-
-	/**
-	 * The Button.blur() method removes focus from the internal button element.
-	 */
-	blur() {
-		if (
-			this[buttonElementSymbol] &&
-			isFunction(this[buttonElementSymbol].blur)
-		) {
-			this[buttonElementSymbol].blur();
-		}
-	}
-
-	/**
-	 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals}
-	 * @return {boolean}
-	 */
-	static get formAssociated() {
-		return true;
-	}
-
-	/**
-	 * The current selection of the Select
-	 *
-	 * ```
-	 * e = document.querySelector('monster-select');
-	 * console.log(e.value)
-	 * // ↦ 1
-	 * // ↦ ['1','2']
-	 * ```
-	 *
-	 * @property {string|array}
-	 */
-	get value() {
-		return this.getOption("value");
-	}
-
-	/**
-	 * Set selection
-	 *
-	 * ```
-	 * e = document.querySelector('monster-select');
-	 * e.value=1
-	 * ```
-	 *
-	 * @property {string|array} value
-	 * @throws {Error} unsupported type
-	 */
-	set value(value) {
-		this.setOption("value", value);
-		try {
-			this?.setFormValue(this.value);
-		} catch (e) {
-			addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
-		}
-	}
+    /**
+     * This method is called by the `instanceof` operator.
+     * @return {symbol}
+     * @since 2.1.0
+     */
+    static get [instanceSymbol]() {
+        return Symbol.for(
+            "@schukai/monster/components/form/popper-button@@instance",
+        );
+    }
+
+    /**
+     * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
+     * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
+     *
+     * The individual configuration values can be found in the table.
+     *
+     * @property {Object} templates - The templates for the control.
+     * @property {string} templates.main - The main template.
+     * @property {object} labels - The labels for the control.
+     * @property {string} labels.button - The label for the button.
+     * @property {string} content - The content of the popper.
+     * @property {object} popper - The popper options.
+     * @extends {Button.defaults}
+     */
+    get defaults() {
+        return Object.assign({}, super.defaults, {
+            templates: {
+                main: getTemplate(),
+            },
+            actions: {
+                click: (e) => {
+                    this.toggleDialog();
+                },
+            },
+            classes: {
+                button: "monster-button",
+            },
+            labels: {
+                button: '<slot name="button"></slot>',
+            },
+            mode: "click",
+            value: null,
+        });
+    }
+
+    /**
+     *
+     * @return {Monster.Components.Form.PopperButton}
+     */
+    [assembleMethodSymbol]() {
+        super[assembleMethodSymbol]();
+        initControlReferences.call(this);
+        initEventHandler.call(this);
+
+        return this;
+    }
+
+    /**
+     * @return {string}
+     */
+    static getTag() {
+        return "monster-popper-button";
+    }
+
+    /**
+     * @return {CSSStyleSheet[]}
+     */
+    static getCSSStyleSheet() {
+        const styles = super.getCSSStyleSheet();
+        styles.push(PopperButtonStyleSheet);
+        return styles;
+    }
+
+    /**
+     * @return {void}
+     */
+    connectedCallback() {
+        super.connectedCallback();
+
+        const document = getDocument();
+
+        for (const [, type] of Object.entries(["click", "touch"])) {
+            // close on outside ui-events
+            document.addEventListener(type, this[closeEventHandler]);
+        }
+
+        updatePopper.call(this);
+        attachResizeObserver.call(this);
+    }
+
+    /**
+     * @return {void}
+     */
+    disconnectedCallback() {
+        super.disconnectedCallback();
+
+        // close on outside ui-events
+        for (const [, type] of Object.entries(["click", "touch"])) {
+            document.removeEventListener(type, this[closeEventHandler]);
+        }
+
+        disconnectResizeObserver.call(this);
+    }
+
+    /**
+     * The Button.click() method simulates a click on the internal button element.
+     *
+     * @since 3.27.0
+     * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click}
+     */
+    click() {
+        if (this.getOption("disabled") === true) {
+            return;
+        }
+
+        if (
+            this[buttonElementSymbol] &&
+            isFunction(this[buttonElementSymbol].click)
+        ) {
+            this[buttonElementSymbol].click();
+        }
+    }
+
+    /**
+     * The Button.focus() method sets focus on the internal button element.
+     *
+     * @since 3.27.0
+     * @param {Object} options
+     * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus}
+     */
+    focus(options) {
+        if (this.getOption("disabled") === true) {
+            return;
+        }
+
+        if (
+            this[buttonElementSymbol] &&
+            isFunction(this[buttonElementSymbol].focus)
+        ) {
+            this[buttonElementSymbol].focus(options);
+        }
+    }
+
+    /**
+     * The Button.blur() method removes focus from the internal button element.
+     */
+    blur() {
+        if (
+            this[buttonElementSymbol] &&
+            isFunction(this[buttonElementSymbol].blur)
+        ) {
+            this[buttonElementSymbol].blur();
+        }
+    }
+
+    /**
+     * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals}
+     * @return {boolean}
+     */
+    static get formAssociated() {
+        return true;
+    }
+
+    /**
+     * The current selection of the Select
+     *
+     * ```
+     * e = document.querySelector('monster-select');
+     * console.log(e.value)
+     * // ↦ 1
+     * // ↦ ['1','2']
+     * ```
+     *
+     * @property {string|array}
+     */
+    get value() {
+        return this.getOption("value");
+    }
+
+    /**
+     * Set selection
+     *
+     * ```
+     * e = document.querySelector('monster-select');
+     * e.value=1
+     * ```
+     *
+     * @property {string|array} value
+     * @throws {Error} unsupported type
+     */
+    set value(value) {
+        this.setOption("value", value);
+        try {
+            this?.setFormValue(this.value);
+        } catch (e) {
+            addErrorAttribute(this, e);
+        }
+    }
 }
 
 /**
@@ -346,67 +347,67 @@ class PopperButton extends Popper {
  * @return {initEventHandler}
  */
 function initEventHandler() {
-	this[closeEventHandler] = (event) => {
-		const path = event.composedPath();
-
-		for (const [, element] of Object.entries(path)) {
-			if (element === this) {
-				return;
-			}
-		}
-		this.hideDialog();
-	};
-
-	return this;
+    this[closeEventHandler] = (event) => {
+        const path = event.composedPath();
+
+        for (const [, element] of Object.entries(path)) {
+            if (element === this) {
+                return;
+            }
+        }
+        this.hideDialog();
+    };
+
+    return this;
 }
 
 /**
  * @private
  */
 function attachResizeObserver() {
-	// against flickering
-	this[resizeObserverSymbol] = new ResizeObserver(() => {
-		if (this[timerCallbackSymbol] instanceof DeadMansSwitch) {
-			try {
-				this[timerCallbackSymbol].touch();
-				return;
-			} catch (e) {
-				delete this[timerCallbackSymbol];
-			}
-		}
-
-		this[timerCallbackSymbol] = new DeadMansSwitch(200, () => {
-			updatePopper.call(this);
-		});
-	});
-
-	this[resizeObserverSymbol].observe(this.parentElement);
+    // against flickering
+    this[resizeObserverSymbol] = new ResizeObserver(() => {
+        if (this[timerCallbackSymbol] instanceof DeadMansSwitch) {
+            try {
+                this[timerCallbackSymbol].touch();
+                return;
+            } catch (e) {
+                delete this[timerCallbackSymbol];
+            }
+        }
+
+        this[timerCallbackSymbol] = new DeadMansSwitch(200, () => {
+            updatePopper.call(this);
+        });
+    });
+
+    this[resizeObserverSymbol].observe(this.parentElement);
 }
 
 function disconnectResizeObserver() {
-	if (this[resizeObserverSymbol] instanceof ResizeObserver) {
-		this[resizeObserverSymbol].disconnect();
-	}
+    if (this[resizeObserverSymbol] instanceof ResizeObserver) {
+        this[resizeObserverSymbol].disconnect();
+    }
 }
 
 /**
  * @private
  */
 function updatePopper() {
-	if (this[popperElementSymbol].style.display !== STYLE_DISPLAY_MODE_BLOCK) {
-		return;
-	}
-
-	if (this.getOption("disabled", false) === true) {
-		return;
-	}
-
-	positionPopper.call(
-		this,
-		this[controlElementSymbol],
-		this[popperElementSymbol],
-		this.getOption("popper", {}),
-	);
+    if (this[popperElementSymbol].style.display !== STYLE_DISPLAY_MODE_BLOCK) {
+        return;
+    }
+
+    if (this.getOption("disabled", false) === true) {
+        return;
+    }
+
+    positionPopper.call(
+        this,
+        this[controlElementSymbol],
+        this[popperElementSymbol],
+        this.getOption("popper", {}),
+    );
 }
 
 /**
@@ -414,21 +415,21 @@ function updatePopper() {
  * @return {Select}
  */
 function initControlReferences() {
-	this[controlElementSymbol] = this.shadowRoot.querySelector(
-		`[${ATTRIBUTE_ROLE}=control]`,
-	);
+    this[controlElementSymbol] = this.shadowRoot.querySelector(
+        `[${ATTRIBUTE_ROLE}=control]`,
+    );
 
-	this[buttonElementSymbol] = this.shadowRoot.querySelector(
-		`[${ATTRIBUTE_ROLE}=button]`,
-	);
+    this[buttonElementSymbol] = this.shadowRoot.querySelector(
+        `[${ATTRIBUTE_ROLE}=button]`,
+    );
 
-	this[popperElementSymbol] = this.shadowRoot.querySelector(
-		`[${ATTRIBUTE_ROLE}=popper]`,
-	);
+    this[popperElementSymbol] = this.shadowRoot.querySelector(
+        `[${ATTRIBUTE_ROLE}=popper]`,
+    );
 
-	this[arrowElementSymbol] = this.shadowRoot.querySelector(
-		`[${ATTRIBUTE_ROLE}=arrow]`,
-	);
+    this[arrowElementSymbol] = this.shadowRoot.querySelector(
+        `[${ATTRIBUTE_ROLE}=arrow]`,
+    );
 }
 
 /**
@@ -436,8 +437,8 @@ function initControlReferences() {
  * @return {string}
  */
 function getTemplate() {
-	// language=HTML
-	return `
+    // language=HTML
+    return `
         <div data-monster-role="control" part="control">
             <button data-monster-attributes="disabled path:disabled | if:true, class path:classes.button"
                     data-monster-role="button"
diff --git a/source/components/form/reload.mjs b/source/components/form/reload.mjs
index 86c288af1ef0443fcfc926ec492a9635844cb0d0..2120f17382b436297e094260202a49daf912ac4d 100644
--- a/source/components/form/reload.mjs
+++ b/source/components/form/reload.mjs
@@ -28,6 +28,7 @@ import {
 import { isString } from "../../types/is.mjs";
 import { ATTRIBUTE_FORM_RELOAD, ATTRIBUTE_FORM_URL } from "./constants.mjs";
 import { loadAndAssignContent } from "./util/fetch.mjs";
+import {addErrorAttribute} from "../../dom/error.mjs";
 
 export { Reload };
 
diff --git a/source/components/form/toggle-switch.mjs b/source/components/form/toggle-switch.mjs
index a06fc4fb1b7c10d5b401f323fcad639a99a56020..8cd47552b26d2b9c54fdca917b5885d48b367300 100644
--- a/source/components/form/toggle-switch.mjs
+++ b/source/components/form/toggle-switch.mjs
@@ -31,6 +31,7 @@ import {
 } from "../../dom/constants.mjs";
 import { getWindow } from "../../dom/util.mjs";
 import { fireEvent } from "../../dom/events.mjs";
+import {addErrorAttribute} from "../../dom/error.mjs";
 
 export { ToggleSwitch };
 
@@ -293,9 +294,8 @@ class ToggleSwitch extends CustomControl {
 			return;
 		}
 
-		addAttributeToken(
+		addErrorAttribute(
 			this,
-			ATTRIBUTE_ERRORMESSAGE,
 			'The value "' +
 				value +
 				'" must be "' +
diff --git a/source/components/notify/monitor-attribute-errors.mjs b/source/components/notify/monitor-attribute-errors.mjs
index 39a6b6d277777aa6924650096667dcdabc6463e4..ef54014093714a98f2806d4d72fcc79865e0bed6 100644
--- a/source/components/notify/monitor-attribute-errors.mjs
+++ b/source/components/notify/monitor-attribute-errors.mjs
@@ -192,7 +192,11 @@ function setupMutationObserver(root) {
 
 			fktOpen("MonitorAttributeErrors " + node.tagName);
 			fktLog(node);
-			fktLog(node.getAttribute("data-monster-error"));
+
+			const errors = node.getAttribute("data-monster-error").split("::");
+			errors.forEach((error) => {
+				fktLog(error);
+			});
 			fktClose();
 
 			if (self.getOption("features.notifyUser")) {
diff --git a/source/dom/customelement.mjs b/source/dom/customelement.mjs
index 86afb0d732da4364cf02fe47c526e68e31475e1c..6ed82c75349409eaf878394534715b3980b5b0a9 100644
--- a/source/dom/customelement.mjs
+++ b/source/dom/customelement.mjs
@@ -12,60 +12,59 @@
  * SPDX-License-Identifier: AGPL-3.0
  */
 
-import { findElementWithIdUpwards } from "./util.mjs";
-import { internalSymbol } from "../constants.mjs";
-import { extend } from "../data/extend.mjs";
-import { Pathfinder } from "../data/pathfinder.mjs";
-import { Formatter } from "../text/formatter.mjs";
-
-import { parseDataURL } from "../types/dataurl.mjs";
-import { getGlobalObject } from "../types/global.mjs";
+import {findElementWithIdUpwards, getDocument, getWindow} from "./util.mjs";
+import {internalSymbol} from "../constants.mjs";
+import {extend} from "../data/extend.mjs";
+import {Pathfinder} from "../data/pathfinder.mjs";
+import {Formatter} from "../text/formatter.mjs";
+
+import {parseDataURL} from "../types/dataurl.mjs";
+import {getGlobalObject} from "../types/global.mjs";
 import {
-	isArray,
-	isFunction,
-	isIterable,
-	isObject,
-	isString,
+    isArray,
+    isFunction,
+    isIterable,
+    isObject,
+    isString,
 } from "../types/is.mjs";
-import { Observer } from "../types/observer.mjs";
-import { ProxyObserver } from "../types/proxyobserver.mjs";
+import {Observer} from "../types/observer.mjs";
+import {ProxyObserver} from "../types/proxyobserver.mjs";
 import {
-	validateFunction,
-	validateInstance,
-	validateObject,
+    validateFunction,
+    validateInstance,
+    validateObject,
 } from "../types/validate.mjs";
-import { clone } from "../util/clone.mjs";
+import {clone} from "../util/clone.mjs";
 import {
-	addAttributeToken,
-	getLinkedObjects,
-	hasObjectLink,
+    getLinkedObjects,
+    hasObjectLink,
 } from "./attributes.mjs";
 import {
-	ATTRIBUTE_DISABLED,
-	ATTRIBUTE_ERRORMESSAGE,
-	ATTRIBUTE_OPTIONS,
-	ATTRIBUTE_INIT_CALLBACK,
-	ATTRIBUTE_OPTIONS_SELECTOR,
-	ATTRIBUTE_SCRIPT_HOST,
-	customElementUpdaterLinkSymbol,
-	initControlCallbackName,
+    ATTRIBUTE_DISABLED,
+    ATTRIBUTE_OPTIONS,
+    ATTRIBUTE_INIT_CALLBACK,
+    ATTRIBUTE_OPTIONS_SELECTOR,
+    ATTRIBUTE_SCRIPT_HOST,
+    customElementUpdaterLinkSymbol,
+    initControlCallbackName,
 } from "./constants.mjs";
-import { findDocumentTemplate, Template } from "./template.mjs";
-import { addObjectWithUpdaterToElement } from "./updater.mjs";
-import { instanceSymbol } from "../constants.mjs";
-import { getDocumentTranslations } from "../i18n/translations.mjs";
-import { getSlottedElements } from "./slotted.mjs";
-import { initOptionsFromAttributes } from "./util/init-options-from-attributes.mjs";
-import { setOptionFromAttribute } from "./util/set-option-from-attribute.mjs";
+import {findDocumentTemplate, Template} from "./template.mjs";
+import {addObjectWithUpdaterToElement} from "./updater.mjs";
+import {instanceSymbol} from "../constants.mjs";
+import {getDocumentTranslations} from "../i18n/translations.mjs";
+import {getSlottedElements} from "./slotted.mjs";
+import {initOptionsFromAttributes} from "./util/init-options-from-attributes.mjs";
+import {setOptionFromAttribute} from "./util/set-option-from-attribute.mjs";
+import {addErrorAttribute} from "./error.mjs";
 
 export {
-	CustomElement,
-	initMethodSymbol,
-	assembleMethodSymbol,
-	attributeObserverSymbol,
-	registerCustomElement,
-	getSlottedElements,
-	updaterTransformerMethodsSymbol,
+    CustomElement,
+    initMethodSymbol,
+    assembleMethodSymbol,
+    attributeObserverSymbol,
+    registerCustomElement,
+    getSlottedElements,
+    updaterTransformerMethodsSymbol,
 };
 
 /**
@@ -77,14 +76,14 @@ const initMethodSymbol = Symbol.for("@schukai/monster/dom/@@initMethodSymbol");
  * @type {symbol}
  */
 const assembleMethodSymbol = Symbol.for(
-	"@schukai/monster/dom/@@assembleMethodSymbol",
+    "@schukai/monster/dom/@@assembleMethodSymbol",
 );
 
 /**
  * @type {symbol}
  */
 const updaterTransformerMethodsSymbol = Symbol.for(
-	"@schukai/monster/dom/@@updaterTransformerMethodsSymbol",
+    "@schukai/monster/dom/@@updaterTransformerMethodsSymbol",
 );
 
 /**
@@ -92,7 +91,7 @@ const updaterTransformerMethodsSymbol = Symbol.for(
  * @type {symbol}
  */
 const attributeObserverSymbol = Symbol.for(
-	"@schukai/monster/dom/@@attributeObserver",
+    "@schukai/monster/dom/@@attributeObserver",
 );
 
 /**
@@ -100,7 +99,7 @@ const attributeObserverSymbol = Symbol.for(
  * @type {symbol}
  */
 const attributeMutationObserverSymbol = Symbol(
-	"@schukai/monster/dom/@@mutationObserver",
+    "@schukai/monster/dom/@@mutationObserver",
 );
 
 /**
@@ -189,555 +188,558 @@ const scriptHostElementSymbol = Symbol("scriptHostElement");
  * @summary A base class for HTML5 custom controls.
  */
 class CustomElement extends HTMLElement {
-	/**
-	 * A new object is created. First, the `initOptions` method is called. Here the
-	 * options can be defined in derived classes. Subsequently, the shadowRoot is initialized.
-	 *
-	 * IMPORTANT: CustomControls instances are not created via the constructor, but either via a tag in the HTML or via <code>document.createElement()</code>.
-	 *
-	 * @throws {Error} the option attribute does not contain a valid JSON definition.
-	 */
-	constructor() {
-		super();
-
-		this[attributeObserverSymbol] = {};
-		this[internalSymbol] = new ProxyObserver({
-			options: initOptionsFromAttributes(this, extend({}, this.defaults)),
-		});
-		this[initMethodSymbol]();
-		initOptionObserver.call(this);
-		this[scriptHostElementSymbol] = [];
-	}
-
-	/**
-	 * This method is called by the `instanceof` operator.
-	 *
-	 * @return {symbol}
-	 * @since 2.1.0
-	 */
-	static get [instanceSymbol]() {
-		return Symbol.for("@schukai/monster/dom/custom-element@@instance");
-	}
-
-	/**
-	 * This method determines which attributes are to be
-	 * monitored by `attributeChangedCallback()`. Unfortunately, this method is static.
-	 * Therefore, the `observedAttributes` property cannot be changed during runtime.
-	 *
-	 * @return {string[]}
-	 * @since 1.15.0
-	 */
-	static get observedAttributes() {
-		return [];
-	}
-
-	/**
-	 *
-	 * @param attribute
-	 * @param callback
-	 * @return {CustomElement}
-	 */
-	addAttributeObserver(attribute, callback) {
-		validateFunction(callback);
-		this[attributeObserverSymbol][attribute] = callback;
-		return this;
-	}
-
-	/**
-	 *
-	 * @param attribute
-	 * @return {CustomElement}
-	 */
-	removeAttributeObserver(attribute) {
-		delete this[attributeObserverSymbol][attribute];
-		return this;
-	}
-
-	/**
-	 * The `defaults` property defines the default values for a control. If you want to override these,
-	 * you can use various methods, which are described in the documentation available at
-	 * {@link https://monsterjs.orgendocconfigurate-a-monster-control}.
-	 *
-	 * The individual configuration values are listed below:
-	 *
-	 * More information about the shadowRoot can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow),
-	 * in the [HTML Standard](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements) or in the [WHATWG Wiki](https://wiki.whatwg.org/wiki/Custom_Elements).
-	 *
-	 * More information about the template element can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).
-	 *
-	 * More information about the slot element can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot).
-	 *
-	 * @property {boolean} disabled=false Specifies whether the control is disabled. When present, it makes the element non-mutable, non-focusable, and non-submittable with the form.
-	 * @property {string} shadowMode=open Specifies the mode of the shadow root. When set to `open`, elements in the shadow root are accessible from JavaScript outside the root, while setting it to `closed` denies access to the root's nodes from JavaScript outside it.
-	 * @property {Boolean} delegatesFocus=true Specifies the behavior of the control with respect to focusability. When set to `true`, it mitigates custom element issues around focusability. When a non-focusable part of the shadow DOM is clicked, the first focusable part is given focus, and the shadow host is given any available :focus styling.
-	 * @property {Object} templates Specifies the templates used by the control.
-	 * @property {string} templates.main=undefined Specifies the main template used by the control.
-	 * @property {Object} templateMapping Specifies the mapping of templates.
-	 * @property {Object} templateFormatter Specifies the formatter for the templates.
-	 * @property {Object} templateFormatter.marker Specifies the marker for the templates.
-	 * @property {Function} templateFormatter.marker.open=null Specifies the opening marker for the templates.
-	 * @property {Function} templateFormatter.marker.close=null Specifies the closing marker for the templates.
-	 * @property {Boolean} eventProcessing=false Specifies whether the control processes events.
-	 * @since 1.8.0
-	 */
-	get defaults() {
-		return {
-			disabled: false,
-			shadowMode: "open",
-			delegatesFocus: true,
-			templates: {
-				main: undefined,
-			},
-			templateMapping: {},
-			templateFormatter: {
-				marker: {
-					open: null,
-					close: null,
-				},
-			},
-
-			eventProcessing: false,
-		};
-	}
-
-	/**
-	 * This method updates the labels of the element.
-	 * The labels are defined in the option object.
-	 * The key of the label is used to retrieve the translation from the document.
-	 * If the translation is different from the label, the label is updated.
-	 *
-	 * Before you can use this method, you must have loaded the translations.
-	 *
-	 * @return {CustomElement}
-	 * @throws {Error}  Cannot find an element with translations. Add a translation object to the document.
-	 */
-	updateI18n() {
-		let translations;
-
-		try {
-			translations = getDocumentTranslations();
-		} catch (e) {
-			addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
-			return this;
-		}
-
-		if (!translations) {
-			return this;
-		}
-
-		const labels = this.getOption("labels");
-		if (!(isObject(labels) || isIterable(labels))) {
-			return this;
-		}
-
-		for (const key in labels) {
-			const def = labels[key];
-
-			if (isString(def)) {
-				const text = translations.getText(key, def);
-				if (text !== def) {
-					this.setOption(`labels.${key}`, text);
-				}
-				continue;
-			} else if (isObject(def)) {
-				for (const k in def) {
-					const d = def[k];
-
-					const text = translations.getPluralRuleText(key, k, d);
-					if (!isString(text)) {
-						throw new Error("Invalid labels definition");
-					}
-					if (text !== d) {
-						this.setOption(`labels.${key}.${k}`, text);
-					}
-				}
-				continue;
-			}
-
-			throw new Error("Invalid labels definition");
-		}
-		return this;
-	}
-
-	/**
-	 * The `getTag()` method returns the tag name associated with the custom element. This method should be overwritten
-	 * by the derived class.
-	 *
-	 * Note that there is no check on the name of the tag in this class. It is the responsibility of
-	 * the developer to assign an appropriate tag name. If the name is not valid, the
-	 * `registerCustomElement()` method will issue an error.
-	 *
-	 * @see https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
-	 * @throws {Error} This method must be overridden by the derived class.
-	 * @return {string} The tag name associated with the custom element.
-	 * @since 1.7.0
-	 */
-	static getTag() {
-		throw new Error(
-			"The method `getTag()` must be overridden by the derived class.",
-		);
-	}
-
-	/**
-	 * The `getCSSStyleSheet()` method returns a `CSSStyleSheet` object that defines the styles for the custom element.
-	 * If the environment does not support the `CSSStyleSheet` constructor, then an object can be built using the provided detour.
-	 *
-	 * If `undefined` is returned, then the shadow root does not receive a stylesheet.
-	 *
-	 * Example usage:
-	 *
-	 * ```js
-	 * class MyElement extends CustomElement {
-	 *   static getCSSStyleSheet() {
-	 *       const sheet = new CSSStyleSheet();
-	 *       sheet.replaceSync("p { color: red; }");
-	 *       return sheet;
-	 *   }
-	 * }
-	 * ```
-	 *
-	 * If the environment does not support the `CSSStyleSheet` constructor,
-	 * you can use the following workaround to create the stylesheet:
-	 *
-	 * ```js
-	 * const doc = document.implementation.createHTMLDocument('title');
-	 * let style = doc.createElement("style");
-	 * style.innerHTML = "p { color: red; }";
-	 * style.appendChild(document.createTextNode(""));
-	 * doc.head.appendChild(style);
-	 * return doc.styleSheets[0];
-	 * ```
-	 *
-	 * @return {CSSStyleSheet|CSSStyleSheet[]|string|undefined} A `CSSStyleSheet` object or an array of such objects that define the styles for the custom element, or `undefined` if no stylesheet should be applied.
-	 */
-	static getCSSStyleSheet() {
-		return undefined;
-	}
-
-	/**
-	 * attach a new observer
-	 *
-	 * @param {Observer} observer
-	 * @return {CustomElement}
-	 */
-	attachObserver(observer) {
-		this[internalSymbol].attachObserver(observer);
-		return this;
-	}
-
-	/**
-	 * detach a observer
-	 *
-	 * @param {Observer} observer
-	 * @return {CustomElement}
-	 */
-	detachObserver(observer) {
-		this[internalSymbol].detachObserver(observer);
-		return this;
-	}
-
-	/**
-	 * @param {Observer} observer
-	 * @return {ProxyObserver}
-	 */
-	containsObserver(observer) {
-		return this[internalSymbol].containsObserver(observer);
-	}
-
-	/**
-	 * nested options can be specified by path `a.b.c`
-	 *
-	 * @param {string} path
-	 * @param {*} defaultValue
-	 * @return {*}
-	 * @since 1.10.0
-	 */
-	getOption(path, defaultValue = undefined) {
-		let value;
-
-		try {
-			value = new Pathfinder(
-				this[internalSymbol].getRealSubject()["options"],
-			).getVia(path);
-		} catch (e) {}
-
-		if (value === undefined) return defaultValue;
-		return value;
-	}
-
-	/**
-	 * Set option and inform elements
-	 *
-	 * @param {string} path
-	 * @param {*} value
-	 * @return {CustomElement}
-	 * @since 1.14.0
-	 */
-	setOption(path, value) {
-		new Pathfinder(this[internalSymbol].getSubject()["options"]).setVia(
-			path,
-			value,
-		);
-		return this;
-	}
-
-	/**
-	 * @since 1.15.0
-	 * @param {string|object} options
-	 * @return {CustomElement}
-	 */
-	setOptions(options) {
-		if (isString(options)) {
-			options = parseOptionsJSON.call(this, options);
-		}
-		// 2024-01-21: remove this.defaults, otherwise it will overwrite
-		// the current settings that have already been made.
-		// https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/136
-		extend(this[internalSymbol].getSubject()["options"], options);
-
-		return this;
-	}
-
-	/**
-	 * Is called once via the constructor
-	 *
-	 * @return {CustomElement}
-	 * @since 1.8.0
-	 */
-	[initMethodSymbol]() {
-		return this;
-	}
-
-	/**
-	 * This method is called once when the object is equipped with update for the dynamic change of the dom.
-	 * The functions returned here can be used as pipe functions in the template.
-	 *
-	 * In the example, the function `my-transformer` is defined. In the template, you can use it as follows:
-	 *
-	 * ```html
-	 * <my-element
-	 *   data-monster-option-transformer="path:my-value | call:my-transformer">
-	 * </my-element>
-	 * ```
-	 *
-	 * The function `my-transformer` is called with the value of `my-value` as a parameter.
-	 *
-	 * ```js
-	 * class MyElement extends CustomElement {
-	 * [updaterTransformerMethodsSymbol]() {
-	 *    return {
-	 *       "my-transformer": (value) => {
-	 *           switch (typeof Wert) {
-	 *           case "string":
-	 *               return value + "!";
-	 *           case "Zahl":
-	 *               return value + 1;
-	 *           default:
-	 *               return value;
-	 *           }
-	 *    }
-	 *    };
-	 *  };
-	 *  }
-	 * ```
-	 *
-	 * @return {object}
-	 * @since 2.43.0
-	 */
-	[updaterTransformerMethodsSymbol]() {
-		return {};
-	}
-
-	/**
-	 * This method is called once when the object is included in the DOM for the first time. It performs the following actions:
-	 *
-	 * <ol>
-	 * <li>Extracts the options from the attributes and the script tag of the element and sets them.</li>
-	 * <li>Initializes the shadow root and its CSS stylesheet (if specified).</li>
-	 * <li>Initializes the HTML content of the element.</li>
-	 * <li>Initializes the custom elements inside the shadow root and the slotted elements.</li>
-	 * <li>Attaches a mutation observer to observe changes to the attributes of the element.</li>
-	 *
-	 * @return {CustomElement} - The updated custom element.
-	 * @since 1.8.0
-	 */
-	[assembleMethodSymbol]() {
-		let elements;
-		let nodeList;
-
-		// Extract options from attributes and set them
-		const AttributeOptions = getOptionsFromAttributes.call(this);
-		if (
-			isObject(AttributeOptions) &&
-			Object.keys(AttributeOptions).length > 0
-		) {
-			this.setOptions(AttributeOptions);
-		}
-
-		// Extract options from script tag and set them
-		const ScriptOptions = getOptionsFromScriptTag.call(this);
-		if (isObject(ScriptOptions) && Object.keys(ScriptOptions).length > 0) {
-			this.setOptions(ScriptOptions);
-		}
-
-		// Initialize the shadow root and its CSS stylesheet
-		if (this.getOption("shadowMode", false) !== false) {
-			try {
-				initShadowRoot.call(this);
-				elements = this.shadowRoot.childNodes;
-			} catch (e) {
-				addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
-			}
-
-			try {
-				initCSSStylesheet.call(this);
-			} catch (e) {
-				addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
-			}
-		}
-
-		// If the elements are not found inside the shadow root, initialize the HTML content of the element
-		if (!(elements instanceof NodeList)) {
-			initHtmlContent.call(this);
-			elements = this.childNodes;
-		}
-
-		// Initialize the custom elements inside the shadow root and the slotted elements
-		initFromCallbackHost.call(this);
-		try {
-			nodeList = new Set([...elements, ...getSlottedElements.call(this)]);
-		} catch (e) {
-			nodeList = elements;
-		}
-
-		try {
-			this[updateCloneDataSymbol] = clone(
-				this[internalSymbol].getRealSubject()["options"],
-			);
-		} catch (e) {
-			addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e?.messages || `${e}`);
-		}
-
-		const cfg = {};
-		if (this.getOption("eventProcessing") === true) {
-			cfg.eventProcessing = true;
-		}
-
-		addObjectWithUpdaterToElement.call(
-			this,
-			nodeList,
-			customElementUpdaterLinkSymbol,
-			this[updateCloneDataSymbol],
-			cfg,
-		);
-
-		// Attach a mutation observer to observe changes to the attributes of the element
-		attachAttributeChangeMutationObserver.call(this);
-
-		return this;
-	}
-
-	/**
-	 * You know what you are doing? This function is only for advanced users.
-	 * The result is a clone of the internal data.
-	 *
-	 * @return {*}
-	 */
-	getInternalUpdateCloneData() {
-		return clone(this[updateCloneDataSymbol]);
-	}
-
-	/**
-	 * This method is called every time the element is inserted into the DOM. It checks if the custom element
-	 * has already been initialized and if not, calls the assembleMethod to initialize it.
-	 *
-	 * @return {void}
-	 * @since 1.7.0
-	 * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/connectedCallback
-	 */
-	connectedCallback() {
-		// Check if the object has already been initialized
-		if (!hasObjectLink(this, customElementUpdaterLinkSymbol)) {
-			// If not, call the assembleMethod to initialize the object
-			this[assembleMethodSymbol]();
-		}
-	}
-
-	/**
-	 * Called every time the element is removed from the DOM. Useful for running clean up code.
-	 *
-	 * @return {void}
-	 * @since 1.7.0
-	 */
-	disconnectedCallback() {}
-
-	/**
-	 * The custom element has been moved into a new document (e.g. someone called document.adoptNode(el)).
-	 *
-	 * @return {void}
-	 * @since 1.7.0
-	 */
-	adoptedCallback() {}
-
-	/**
-	 * Called when an observed attribute has been added, removed, updated, or replaced. Also called for initial
-	 * values when an element is created by the parser, or upgraded. Note: only attributes listed in the observedAttributes
-	 * property will receive this callback.
-	 *
-	 * @param {string} attrName
-	 * @param {string} oldVal
-	 * @param {string} newVal
-	 * @return {void}
-	 * @since 1.15.0
-	 */
-	attributeChangedCallback(attrName, oldVal, newVal) {
-		if (attrName.startsWith("data-monster-option-")) {
-			setOptionFromAttribute(
-				this,
-				attrName,
-				this[internalSymbol].getSubject()["options"],
-			);
-		}
-
-		const callback = this[attributeObserverSymbol]?.[attrName];
-		if (isFunction(callback)) {
-			try {
-				callback.call(this, newVal, oldVal);
-			} catch (e) {
-				addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
-			}
-		}
-	}
-
-	/**
-	 *
-	 * @param {Node} node
-	 * @return {boolean}
-	 * @throws {TypeError} value is not an instance of
-	 * @since 1.19.0
-	 */
-	hasNode(node) {
-		if (containChildNode.call(this, validateInstance(node, Node))) {
-			return true;
-		}
-
-		if (!(this.shadowRoot instanceof ShadowRoot)) {
-			return false;
-		}
-
-		return containChildNode.call(this.shadowRoot, node);
-	}
-
-	/**
-	 * Calls a callback function if it exists.
-	 *
-	 * @param {string} name
-	 * @param {*} args
-	 * @return {*}
-	 */
-	callCallback(name, args) {
-		return callControlCallback.call(this, name, ...args);
-	}
+    /**
+     * A new object is created. First, the `initOptions` method is called. Here the
+     * options can be defined in derived classes. Subsequently, the shadowRoot is initialized.
+     *
+     * IMPORTANT: CustomControls instances are not created via the constructor, but either via a tag in the HTML or via <code>document.createElement()</code>.
+     *
+     * @throws {Error} the option attribute does not contain a valid JSON definition.
+     */
+    constructor() {
+        super();
+
+        this[attributeObserverSymbol] = {};
+        this[internalSymbol] = new ProxyObserver({
+            options: initOptionsFromAttributes(this, extend({}, this.defaults)),
+        });
+        this[initMethodSymbol]();
+        initOptionObserver.call(this);
+        this[scriptHostElementSymbol] = [];
+    }
+
+    /**
+     * This method is called by the `instanceof` operator.
+     *
+     * @return {symbol}
+     * @since 2.1.0
+     */
+    static get [instanceSymbol]() {
+        return Symbol.for("@schukai/monster/dom/custom-element@@instance");
+    }
+
+    /**
+     * This method determines which attributes are to be
+     * monitored by `attributeChangedCallback()`. Unfortunately, this method is static.
+     * Therefore, the `observedAttributes` property cannot be changed during runtime.
+     *
+     * @return {string[]}
+     * @since 1.15.0
+     */
+    static get observedAttributes() {
+        return [];
+    }
+
+    /**
+     *
+     * @param attribute
+     * @param callback
+     * @return {CustomElement}
+     */
+    addAttributeObserver(attribute, callback) {
+        validateFunction(callback);
+        this[attributeObserverSymbol][attribute] = callback;
+        return this;
+    }
+
+    /**
+     *
+     * @param attribute
+     * @return {CustomElement}
+     */
+    removeAttributeObserver(attribute) {
+        delete this[attributeObserverSymbol][attribute];
+        return this;
+    }
+
+    /**
+     * The `defaults` property defines the default values for a control. If you want to override these,
+     * you can use various methods, which are described in the documentation available at
+     * {@link https://monsterjs.orgendocconfigurate-a-monster-control}.
+     *
+     * The individual configuration values are listed below:
+     *
+     * More information about the shadowRoot can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow),
+     * in the [HTML Standard](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements) or in the [WHATWG Wiki](https://wiki.whatwg.org/wiki/Custom_Elements).
+     *
+     * More information about the template element can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).
+     *
+     * More information about the slot element can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot).
+     *
+     * @property {boolean} disabled=false Specifies whether the control is disabled. When present, it makes the element non-mutable, non-focusable, and non-submittable with the form.
+     * @property {string} shadowMode=open Specifies the mode of the shadow root. When set to `open`, elements in the shadow root are accessible from JavaScript outside the root, while setting it to `closed` denies access to the root's nodes from JavaScript outside it.
+     * @property {Boolean} delegatesFocus=true Specifies the behavior of the control with respect to focusability. When set to `true`, it mitigates custom element issues around focusability. When a non-focusable part of the shadow DOM is clicked, the first focusable part is given focus, and the shadow host is given any available :focus styling.
+     * @property {Object} templates Specifies the templates used by the control.
+     * @property {string} templates.main=undefined Specifies the main template used by the control.
+     * @property {Object} templateMapping Specifies the mapping of templates.
+     * @property {Object} templateFormatter Specifies the formatter for the templates.
+     * @property {Object} templateFormatter.marker Specifies the marker for the templates.
+     * @property {Function} templateFormatter.marker.open=null Specifies the opening marker for the templates.
+     * @property {Function} templateFormatter.marker.close=null Specifies the closing marker for the templates.
+     * @property {Boolean} eventProcessing=false Specifies whether the control processes events.
+     * @since 1.8.0
+     */
+    get defaults() {
+        return {
+            disabled: false,
+            shadowMode: "open",
+            delegatesFocus: true,
+            templates: {
+                main: undefined,
+            },
+            templateMapping: {},
+            templateFormatter: {
+                marker: {
+                    open: null,
+                    close: null,
+                },
+            },
+
+            eventProcessing: false,
+        };
+    }
+
+    /**
+     * This method updates the labels of the element.
+     * The labels are defined in the option object.
+     * The key of the label is used to retrieve the translation from the document.
+     * If the translation is different from the label, the label is updated.
+     *
+     * Before you can use this method, you must have loaded the translations.
+     *
+     * @return {CustomElement}
+     * @throws {Error}  Cannot find an element with translations. Add a translation object to the document.
+     */
+    updateI18n() {
+        let translations;
+
+        try {
+            translations = getDocumentTranslations();
+        } catch (e) {
+            addErrorAttribute(this, e);
+            return this;
+        }
+
+        if (!translations) {
+            return this;
+        }
+
+        const labels = this.getOption("labels");
+        if (!(isObject(labels) || isIterable(labels))) {
+            return this;
+        }
+
+        for (const key in labels) {
+            const def = labels[key];
+
+            if (isString(def)) {
+                const text = translations.getText(key, def);
+                if (text !== def) {
+                    this.setOption(`labels.${key}`, text);
+                }
+                continue;
+            } else if (isObject(def)) {
+                for (const k in def) {
+                    const d = def[k];
+
+                    const text = translations.getPluralRuleText(key, k, d);
+                    if (!isString(text)) {
+                        throw new Error("Invalid labels definition");
+                    }
+                    if (text !== d) {
+                        this.setOption(`labels.${key}.${k}`, text);
+                    }
+                }
+                continue;
+            }
+
+            throw new Error("Invalid labels definition");
+        }
+        return this;
+    }
+
+    /**
+     * The `getTag()` method returns the tag name associated with the custom element. This method should be overwritten
+     * by the derived class.
+     *
+     * Note that there is no check on the name of the tag in this class. It is the responsibility of
+     * the developer to assign an appropriate tag name. If the name is not valid, the
+     * `registerCustomElement()` method will issue an error.
+     *
+     * @see https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
+     * @throws {Error} This method must be overridden by the derived class.
+     * @return {string} The tag name associated with the custom element.
+     * @since 1.7.0
+     */
+    static getTag() {
+        throw new Error(
+            "The method `getTag()` must be overridden by the derived class.",
+        );
+    }
+
+    /**
+     * The `getCSSStyleSheet()` method returns a `CSSStyleSheet` object that defines the styles for the custom element.
+     * If the environment does not support the `CSSStyleSheet` constructor, then an object can be built using the provided detour.
+     *
+     * If `undefined` is returned, then the shadow root does not receive a stylesheet.
+     *
+     * Example usage:
+     *
+     * ```js
+     * class MyElement extends CustomElement {
+     *   static getCSSStyleSheet() {
+     *       const sheet = new CSSStyleSheet();
+     *       sheet.replaceSync("p { color: red; }");
+     *       return sheet;
+     *   }
+     * }
+     * ```
+     *
+     * If the environment does not support the `CSSStyleSheet` constructor,
+     * you can use the following workaround to create the stylesheet:
+     *
+     * ```js
+     * const doc = document.implementation.createHTMLDocument('title');
+     * let style = doc.createElement("style");
+     * style.innerHTML = "p { color: red; }";
+     * style.appendChild(document.createTextNode(""));
+     * doc.head.appendChild(style);
+     * return doc.styleSheets[0];
+     * ```
+     *
+     * @return {CSSStyleSheet|CSSStyleSheet[]|string|undefined} A `CSSStyleSheet` object or an array of such objects that define the styles for the custom element, or `undefined` if no stylesheet should be applied.
+     */
+    static getCSSStyleSheet() {
+        return undefined;
+    }
+
+    /**
+     * attach a new observer
+     *
+     * @param {Observer} observer
+     * @return {CustomElement}
+     */
+    attachObserver(observer) {
+        this[internalSymbol].attachObserver(observer);
+        return this;
+    }
+
+    /**
+     * detach a observer
+     *
+     * @param {Observer} observer
+     * @return {CustomElement}
+     */
+    detachObserver(observer) {
+        this[internalSymbol].detachObserver(observer);
+        return this;
+    }
+
+    /**
+     * @param {Observer} observer
+     * @return {ProxyObserver}
+     */
+    containsObserver(observer) {
+        return this[internalSymbol].containsObserver(observer);
+    }
+
+    /**
+     * nested options can be specified by path `a.b.c`
+     *
+     * @param {string} path
+     * @param {*} defaultValue
+     * @return {*}
+     * @since 1.10.0
+     */
+    getOption(path, defaultValue = undefined) {
+        let value;
+
+        try {
+            value = new Pathfinder(
+                this[internalSymbol].getRealSubject()["options"],
+            ).getVia(path);
+        } catch (e) {
+        }
+
+        if (value === undefined) return defaultValue;
+        return value;
+    }
+
+    /**
+     * Set option and inform elements
+     *
+     * @param {string} path
+     * @param {*} value
+     * @return {CustomElement}
+     * @since 1.14.0
+     */
+    setOption(path, value) {
+        new Pathfinder(this[internalSymbol].getSubject()["options"]).setVia(
+            path,
+            value,
+        );
+        return this;
+    }
+
+    /**
+     * @since 1.15.0
+     * @param {string|object} options
+     * @return {CustomElement}
+     */
+    setOptions(options) {
+        if (isString(options)) {
+            options = parseOptionsJSON.call(this, options);
+        }
+        // 2024-01-21: remove this.defaults, otherwise it will overwrite
+        // the current settings that have already been made.
+        // https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/136
+        extend(this[internalSymbol].getSubject()["options"], options);
+
+        return this;
+    }
+
+    /**
+     * Is called once via the constructor
+     *
+     * @return {CustomElement}
+     * @since 1.8.0
+     */
+    [initMethodSymbol]() {
+        return this;
+    }
+
+    /**
+     * This method is called once when the object is equipped with update for the dynamic change of the dom.
+     * The functions returned here can be used as pipe functions in the template.
+     *
+     * In the example, the function `my-transformer` is defined. In the template, you can use it as follows:
+     *
+     * ```html
+     * <my-element
+     *   data-monster-option-transformer="path:my-value | call:my-transformer">
+     * </my-element>
+     * ```
+     *
+     * The function `my-transformer` is called with the value of `my-value` as a parameter.
+     *
+     * ```js
+     * class MyElement extends CustomElement {
+     * [updaterTransformerMethodsSymbol]() {
+     *    return {
+     *       "my-transformer": (value) => {
+     *           switch (typeof Wert) {
+     *           case "string":
+     *               return value + "!";
+     *           case "Zahl":
+     *               return value + 1;
+     *           default:
+     *               return value;
+     *           }
+     *    }
+     *    };
+     *  };
+     *  }
+     * ```
+     *
+     * @return {object}
+     * @since 2.43.0
+     */
+    [updaterTransformerMethodsSymbol]() {
+        return {};
+    }
+
+    /**
+     * This method is called once when the object is included in the DOM for the first time. It performs the following actions:
+     *
+     * <ol>
+     * <li>Extracts the options from the attributes and the script tag of the element and sets them.</li>
+     * <li>Initializes the shadow root and its CSS stylesheet (if specified).</li>
+     * <li>Initializes the HTML content of the element.</li>
+     * <li>Initializes the custom elements inside the shadow root and the slotted elements.</li>
+     * <li>Attaches a mutation observer to observe changes to the attributes of the element.</li>
+     *
+     * @return {CustomElement} - The updated custom element.
+     * @since 1.8.0
+     */
+    [assembleMethodSymbol]() {
+        let elements;
+        let nodeList;
+
+        // Extract options from attributes and set them
+        const AttributeOptions = getOptionsFromAttributes.call(this);
+        if (
+            isObject(AttributeOptions) &&
+            Object.keys(AttributeOptions).length > 0
+        ) {
+            this.setOptions(AttributeOptions);
+        }
+
+        // Extract options from script tag and set them
+        const ScriptOptions = getOptionsFromScriptTag.call(this);
+        if (isObject(ScriptOptions) && Object.keys(ScriptOptions).length > 0) {
+            this.setOptions(ScriptOptions);
+        }
+
+        // Initialize the shadow root and its CSS stylesheet
+        if (this.getOption("shadowMode", false) !== false) {
+            try {
+                initShadowRoot.call(this);
+                elements = this.shadowRoot.childNodes;
+            } catch (e) {
+                addErrorAttribute(this, e);
+            }
+
+            try {
+                initCSSStylesheet.call(this);
+            } catch (e) {
+                addErrorAttribute(this, e);
+            }
+        }
+
+        // If the elements are not found inside the shadow root, initialize the HTML content of the element
+        if (!(elements instanceof NodeList)) {
+            initHtmlContent.call(this);
+            elements = this.childNodes;
+        }
+
+        // Initialize the custom elements inside the shadow root and the slotted elements
+        initFromCallbackHost.call(this);
+        try {
+            nodeList = new Set([...elements, ...getSlottedElements.call(this)]);
+        } catch (e) {
+            nodeList = elements;
+        }
+
+        try {
+            this[updateCloneDataSymbol] = clone(
+                this[internalSymbol].getRealSubject()["options"],
+            );
+        } catch (e) {
+            addErrorAttribute(this, e);
+        }
+
+        const cfg = {};
+        if (this.getOption("eventProcessing") === true) {
+            cfg.eventProcessing = true;
+        }
+
+        addObjectWithUpdaterToElement.call(
+            this,
+            nodeList,
+            customElementUpdaterLinkSymbol,
+            this[updateCloneDataSymbol],
+            cfg,
+        );
+
+        // Attach a mutation observer to observe changes to the attributes of the element
+        attachAttributeChangeMutationObserver.call(this);
+
+        return this;
+    }
+
+    /**
+     * You know what you are doing? This function is only for advanced users.
+     * The result is a clone of the internal data.
+     *
+     * @return {*}
+     */
+    getInternalUpdateCloneData() {
+        return clone(this[updateCloneDataSymbol]);
+    }
+
+    /**
+     * This method is called every time the element is inserted into the DOM. It checks if the custom element
+     * has already been initialized and if not, calls the assembleMethod to initialize it.
+     *
+     * @return {void}
+     * @since 1.7.0
+     * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/connectedCallback
+     */
+    connectedCallback() {
+        // Check if the object has already been initialized
+        if (!hasObjectLink(this, customElementUpdaterLinkSymbol)) {
+            // If not, call the assembleMethod to initialize the object
+            this[assembleMethodSymbol]();
+        }
+    }
+
+    /**
+     * Called every time the element is removed from the DOM. Useful for running clean up code.
+     *
+     * @return {void}
+     * @since 1.7.0
+     */
+    disconnectedCallback() {
+    }
+
+    /**
+     * The custom element has been moved into a new document (e.g. someone called document.adoptNode(el)).
+     *
+     * @return {void}
+     * @since 1.7.0
+     */
+    adoptedCallback() {
+    }
+
+    /**
+     * Called when an observed attribute has been added, removed, updated, or replaced. Also called for initial
+     * values when an element is created by the parser, or upgraded. Note: only attributes listed in the observedAttributes
+     * property will receive this callback.
+     *
+     * @param {string} attrName
+     * @param {string} oldVal
+     * @param {string} newVal
+     * @return {void}
+     * @since 1.15.0
+     */
+    attributeChangedCallback(attrName, oldVal, newVal) {
+        if (attrName.startsWith("data-monster-option-")) {
+            setOptionFromAttribute(
+                this,
+                attrName,
+                this[internalSymbol].getSubject()["options"],
+            );
+        }
+
+        const callback = this[attributeObserverSymbol]?.[attrName];
+        if (isFunction(callback)) {
+            try {
+                callback.call(this, newVal, oldVal);
+            } catch (e) {
+                addErrorAttribute(this, e);
+            }
+        }
+    }
+
+    /**
+     *
+     * @param {Node} node
+     * @return {boolean}
+     * @throws {TypeError} value is not an instance of
+     * @since 1.19.0
+     */
+    hasNode(node) {
+        if (containChildNode.call(this, validateInstance(node, Node))) {
+            return true;
+        }
+
+        if (!(this.shadowRoot instanceof ShadowRoot)) {
+            return false;
+        }
+
+        return containChildNode.call(this.shadowRoot, node);
+    }
+
+    /**
+     * Calls a callback function if it exists.
+     *
+     * @param {string} name
+     * @param {*} args
+     * @return {*}
+     */
+    callCallback(name, args) {
+        return callControlCallback.call(this, name, ...args);
+    }
 }
 
 /**
@@ -746,50 +748,49 @@ class CustomElement extends HTMLElement {
  * @return {any}
  */
 function callControlCallback(callBackFunctionName, ...args) {
-	if (!isString(callBackFunctionName) || callBackFunctionName === "") {
-		return;
-	}
-
-	if (callBackFunctionName in this) {
-		return this[callBackFunctionName](this, ...args);
-	}
-
-	if (!this.hasAttribute(ATTRIBUTE_SCRIPT_HOST)) {
-		return;
-	}
-
-	if (this[scriptHostElementSymbol].length === 0) {
-		const targetId = this.getAttribute(ATTRIBUTE_SCRIPT_HOST);
-		if (!targetId) {
-			return;
-		}
-
-		const list = targetId.split(",");
-		for (const id of list) {
-			const host = findElementWithIdUpwards(this, targetId);
-			if (!(host instanceof HTMLElement)) {
-				continue;
-			}
-
-			this[scriptHostElementSymbol].push(host);
-		}
-	}
-
-	for (const host of this[scriptHostElementSymbol]) {
-		if (callBackFunctionName in host) {
-			try {
-				return host[callBackFunctionName](this, ...args);
-			} catch (e) {
-				addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString());
-			}
-		}
-	}
-
-	addAttributeToken(
-		this,
-		ATTRIBUTE_ERRORMESSAGE,
-		`callback ${callBackFunctionName} not found`,
-	);
+    if (!isString(callBackFunctionName) || callBackFunctionName === "") {
+        return;
+    }
+
+    if (callBackFunctionName in this) {
+        return this[callBackFunctionName](this, ...args);
+    }
+
+    if (!this.hasAttribute(ATTRIBUTE_SCRIPT_HOST)) {
+        return;
+    }
+
+    if (this[scriptHostElementSymbol].length === 0) {
+        const targetId = this.getAttribute(ATTRIBUTE_SCRIPT_HOST);
+        if (!targetId) {
+            return;
+        }
+
+        const list = targetId.split(",");
+        for (const id of list) {
+            const host = findElementWithIdUpwards(this, targetId);
+            if (!(host instanceof HTMLElement)) {
+                continue;
+            }
+
+            this[scriptHostElementSymbol].push(host);
+        }
+    }
+
+    for (const host of this[scriptHostElementSymbol]) {
+        if (callBackFunctionName in host) {
+            try {
+                return host[callBackFunctionName](this, ...args);
+            } catch (e) {
+                addErrorAttribute(this, e);
+            }
+        }
+    }
+
+    addErrorAttribute(
+        this,
+        `callback ${callBackFunctionName} not found`,
+    );
 }
 
 /**
@@ -806,16 +807,16 @@ function callControlCallback(callBackFunctionName, ...args) {
  * @since 1.8.0
  */
 function initFromCallbackHost() {
-	// Set the default callback function name
-	let callBackFunctionName = initControlCallbackName;
+    // Set the default callback function name
+    let callBackFunctionName = initControlCallbackName;
 
-	// If the `data-monster-option-callback` attribute is set, use its value as the callback function name
-	if (this.hasAttribute(ATTRIBUTE_INIT_CALLBACK)) {
-		callBackFunctionName = this.getAttribute(ATTRIBUTE_INIT_CALLBACK);
-	}
+    // If the `data-monster-option-callback` attribute is set, use its value as the callback function name
+    if (this.hasAttribute(ATTRIBUTE_INIT_CALLBACK)) {
+        callBackFunctionName = this.getAttribute(ATTRIBUTE_INIT_CALLBACK);
+    }
 
-	// Call the callback function with the element as a parameter if it exists
-	callControlCallback.call(this, callBackFunctionName);
+    // Call the callback function with the element as a parameter if it exists
+    callControlCallback.call(this, callBackFunctionName);
 }
 
 /**
@@ -825,34 +826,34 @@ function initFromCallbackHost() {
  * @this CustomElement
  */
 function attachAttributeChangeMutationObserver() {
-	const self = this;
-
-	if (typeof self[attributeMutationObserverSymbol] !== "undefined") {
-		return;
-	}
-
-	self[attributeMutationObserverSymbol] = new MutationObserver(
-		function (mutations, observer) {
-			for (const mutation of mutations) {
-				if (mutation.type === "attributes") {
-					self.attributeChangedCallback(
-						mutation.attributeName,
-						mutation.oldValue,
-						mutation.target.getAttribute(mutation.attributeName),
-					);
-				}
-			}
-		},
-	);
-
-	try {
-		self[attributeMutationObserverSymbol].observe(self, {
-			attributes: true,
-			attributeOldValue: true,
-		});
-	} catch (e) {
-		addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString());
-	}
+    const self = this;
+
+    if (typeof self[attributeMutationObserverSymbol] !== "undefined") {
+        return;
+    }
+
+    self[attributeMutationObserverSymbol] = new MutationObserver(
+        function (mutations, observer) {
+            for (const mutation of mutations) {
+                if (mutation.type === "attributes") {
+                    self.attributeChangedCallback(
+                        mutation.attributeName,
+                        mutation.oldValue,
+                        mutation.target.getAttribute(mutation.attributeName),
+                    );
+                }
+            }
+        },
+    );
+
+    try {
+        self[attributeMutationObserverSymbol].observe(self, {
+            attributes: true,
+            attributeOldValue: true,
+        });
+    } catch (e) {
+        addErrorAttribute(self, e);
+    }
 }
 
 /**
@@ -862,19 +863,19 @@ function attachAttributeChangeMutationObserver() {
  * @return {boolean}
  */
 function containChildNode(node) {
-	if (this.contains(node)) {
-		return true;
-	}
+    if (this.contains(node)) {
+        return true;
+    }
 
-	for (const [, e] of Object.entries(this.childNodes)) {
-		if (e.contains(node)) {
-			return true;
-		}
+    for (const [, e] of Object.entries(this.childNodes)) {
+        if (e.contains(node)) {
+            return true;
+        }
 
-		containChildNode.call(e, node);
-	}
+        containChildNode.call(e, node);
+    }
 
-	return false;
+    return false;
 }
 
 /**
@@ -884,89 +885,89 @@ function containChildNode(node) {
  * @this CustomElement
  */
 function initOptionObserver() {
-	const self = this;
-
-	let lastDisabledValue = undefined;
-	self.attachObserver(
-		new Observer(function () {
-			const flag = self.getOption("disabled");
-
-			if (flag === lastDisabledValue) {
-				return;
-			}
-
-			lastDisabledValue = flag;
-
-			if (!(self.shadowRoot instanceof ShadowRoot)) {
-				return;
-			}
-
-			const query =
-				"button, command, fieldset, keygen, optgroup, option, select, textarea, input, [data-monster-objectlink]";
-			const elements = self.shadowRoot.querySelectorAll(query);
-
-			let nodeList;
-			try {
-				nodeList = new Set([
-					...elements,
-					...getSlottedElements.call(self, query),
-				]);
-			} catch (e) {
-				nodeList = elements;
-			}
-
-			for (const element of [...nodeList]) {
-				if (flag === true) {
-					element.setAttribute(ATTRIBUTE_DISABLED, "");
-				} else {
-					element.removeAttribute(ATTRIBUTE_DISABLED);
-				}
-			}
-		}),
-	);
-
-	self.attachObserver(
-		new Observer(function () {
-			// not initialised
-			if (!hasObjectLink(self, customElementUpdaterLinkSymbol)) {
-				return;
-			}
-			// inform every element
-			const updaters = getLinkedObjects(self, customElementUpdaterLinkSymbol);
-
-			for (const list of updaters) {
-				for (const updater of list) {
-					const d = clone(self[internalSymbol].getRealSubject()["options"]);
-					Object.assign(updater.getSubject(), d);
-				}
-			}
-		}),
-	);
-
-	// disabled
-	self[attributeObserverSymbol][ATTRIBUTE_DISABLED] = () => {
-		if (self.hasAttribute(ATTRIBUTE_DISABLED)) {
-			self.setOption(ATTRIBUTE_DISABLED, true);
-		} else {
-			self.setOption(ATTRIBUTE_DISABLED, undefined);
-		}
-	};
-
-	// data-monster-options
-	self[attributeObserverSymbol][ATTRIBUTE_OPTIONS] = () => {
-		const options = getOptionsFromAttributes.call(self);
-		if (isObject(options) && Object.keys(options).length > 0) {
-			self.setOptions(options);
-		}
-	};
-
-	// data-monster-options-selector
-	self[attributeObserverSymbol][ATTRIBUTE_OPTIONS_SELECTOR] = () => {
-		const options = getOptionsFromScriptTag.call(self);
-		if (isObject(options) && Object.keys(options).length > 0) {
-			self.setOptions(options);
-		}
-	};
+    const self = this;
+
+    let lastDisabledValue = undefined;
+    self.attachObserver(
+        new Observer(function () {
+            const flag = self.getOption("disabled");
+
+            if (flag === lastDisabledValue) {
+                return;
+            }
+
+            lastDisabledValue = flag;
+
+            if (!(self.shadowRoot instanceof ShadowRoot)) {
+                return;
+            }
+
+            const query =
+                "button, command, fieldset, keygen, optgroup, option, select, textarea, input, [data-monster-objectlink]";
+            const elements = self.shadowRoot.querySelectorAll(query);
+
+            let nodeList;
+            try {
+                nodeList = new Set([
+                    ...elements,
+                    ...getSlottedElements.call(self, query),
+                ]);
+            } catch (e) {
+                nodeList = elements;
+            }
+
+            for (const element of [...nodeList]) {
+                if (flag === true) {
+                    element.setAttribute(ATTRIBUTE_DISABLED, "");
+                } else {
+                    element.removeAttribute(ATTRIBUTE_DISABLED);
+                }
+            }
+        }),
+    );
+
+    self.attachObserver(
+        new Observer(function () {
+            // not initialised
+            if (!hasObjectLink(self, customElementUpdaterLinkSymbol)) {
+                return;
+            }
+            // inform every element
+            const updaters = getLinkedObjects(self, customElementUpdaterLinkSymbol);
+
+            for (const list of updaters) {
+                for (const updater of list) {
+                    const d = clone(self[internalSymbol].getRealSubject()["options"]);
+                    Object.assign(updater.getSubject(), d);
+                }
+            }
+        }),
+    );
+
+    // disabled
+    self[attributeObserverSymbol][ATTRIBUTE_DISABLED] = () => {
+        if (self.hasAttribute(ATTRIBUTE_DISABLED)) {
+            self.setOption(ATTRIBUTE_DISABLED, true);
+        } else {
+            self.setOption(ATTRIBUTE_DISABLED, undefined);
+        }
+    };
+
+    // data-monster-options
+    self[attributeObserverSymbol][ATTRIBUTE_OPTIONS] = () => {
+        const options = getOptionsFromAttributes.call(self);
+        if (isObject(options) && Object.keys(options).length > 0) {
+            self.setOptions(options);
+        }
+    };
+
+    // data-monster-options-selector
+    self[attributeObserverSymbol][ATTRIBUTE_OPTIONS_SELECTOR] = () => {
+        const options = getOptionsFromScriptTag.call(self);
+        if (isObject(options) && Object.keys(options).length > 0) {
+            self.setOptions(options);
+        }
+    };
 }
 
 /**
@@ -975,37 +976,35 @@ function initOptionObserver() {
  * @throws {TypeError} value is not a object
  */
 function getOptionsFromScriptTag() {
-	if (!this.hasAttribute(ATTRIBUTE_OPTIONS_SELECTOR)) {
-		return {};
-	}
-
-	const node = document.querySelector(
-		this.getAttribute(ATTRIBUTE_OPTIONS_SELECTOR),
-	);
-	if (!(node instanceof HTMLScriptElement)) {
-		addAttributeToken(
-			this,
-			ATTRIBUTE_ERRORMESSAGE,
-			`the selector ${ATTRIBUTE_OPTIONS_SELECTOR} for options was specified (${this.getAttribute(
-				ATTRIBUTE_OPTIONS_SELECTOR,
-			)}) but not found.`,
-		);
-		return {};
-	}
-
-	let obj = {};
-
-	try {
-		obj = parseOptionsJSON.call(this, node.textContent.trim());
-	} catch (e) {
-		addAttributeToken(
-			this,
-			ATTRIBUTE_ERRORMESSAGE,
-			`when analyzing the configuration from the script tag there was an error. ${e}`,
-		);
-	}
-
-	return obj;
+    if (!this.hasAttribute(ATTRIBUTE_OPTIONS_SELECTOR)) {
+        return {};
+    }
+
+    const node = document.querySelector(
+        this.getAttribute(ATTRIBUTE_OPTIONS_SELECTOR),
+    );
+    if (!(node instanceof HTMLScriptElement)) {
+        addErrorAttribute(
+            this,
+            `the selector ${ATTRIBUTE_OPTIONS_SELECTOR} for options was specified (${this.getAttribute(
+                ATTRIBUTE_OPTIONS_SELECTOR,
+            )}) but not found.`,
+        );
+        return {};
+    }
+
+    let obj = {};
+
+    try {
+        obj = parseOptionsJSON.call(this, node.textContent.trim());
+    } catch (e) {
+        addErrorAttribute(
+            this,
+            `when analyzing the configuration from the script tag there was an error. ${e}`,
+        );
+    }
+
+    return obj;
 }
 
 /**
@@ -1013,21 +1012,20 @@ function getOptionsFromScriptTag() {
  * @return {object}
  */
 function getOptionsFromAttributes() {
-	if (this.hasAttribute(ATTRIBUTE_OPTIONS)) {
-		try {
-			return parseOptionsJSON.call(this, this.getAttribute(ATTRIBUTE_OPTIONS));
-		} catch (e) {
-			addAttributeToken(
-				this,
-				ATTRIBUTE_ERRORMESSAGE,
-				`the options attribute ${ATTRIBUTE_OPTIONS} does not contain a valid json definition (actual: ${this.getAttribute(
-					ATTRIBUTE_OPTIONS,
-				)}).${e}`,
-			);
-		}
-	}
-
-	return {};
+    if (this.hasAttribute(ATTRIBUTE_OPTIONS)) {
+        try {
+            return parseOptionsJSON.call(this, this.getAttribute(ATTRIBUTE_OPTIONS));
+        } catch (e) {
+            addErrorAttribute(
+                this,
+                `the options attribute ${ATTRIBUTE_OPTIONS} does not contain a valid json definition (actual: ${this.getAttribute(
+                    ATTRIBUTE_OPTIONS,
+                )}).${e}`,
+            );
+        }
+    }
+
+    return {};
 }
 
 /**
@@ -1039,25 +1037,26 @@ function getOptionsFromAttributes() {
  * @throws {error} Throws an error if the JSON data is not valid.
  */
 function parseOptionsJSON(data) {
-	let obj = {};
-
-	if (!isString(data)) {
-		return obj;
-	}
-
-	// the configuration can be specified as a data url.
-	try {
-		const dataUrl = parseDataURL(data);
-		data = dataUrl.content;
-	} catch (e) {}
-
-	try {
-		obj = JSON.parse(data);
-	} catch (e) {
-		throw e;
-	}
-
-	return validateObject(obj);
+    let obj = {};
+
+    if (!isString(data)) {
+        return obj;
+    }
+
+    // the configuration can be specified as a data url.
+    try {
+        const dataUrl = parseDataURL(data);
+        data = dataUrl.content;
+    } catch (e) {
+    }
+
+    try {
+        obj = JSON.parse(data);
+    } catch (e) {
+        throw e;
+    }
+
+    return validateObject(obj);
 }
 
 /**
@@ -1065,21 +1064,21 @@ function parseOptionsJSON(data) {
  * @return {initHtmlContent}
  */
 function initHtmlContent() {
-	try {
-		const template = findDocumentTemplate(this.constructor.getTag());
-		this.appendChild(template.createDocumentFragment());
-	} catch (e) {
-		let html = this.getOption("templates.main", "");
-		if (isString(html) && html.length > 0) {
-			const mapping = this.getOption("templateMapping", {});
-			if (isObject(mapping)) {
-				html = new Formatter(mapping, {}).format(html);
-			}
-			this.innerHTML = html;
-		}
-	}
-
-	return this;
+    try {
+        const template = findDocumentTemplate(this.constructor.getTag());
+        this.appendChild(template.createDocumentFragment());
+    } catch (e) {
+        let html = this.getOption("templates.main", "");
+        if (isString(html) && html.length > 0) {
+            const mapping = this.getOption("templateMapping", {});
+            if (isObject(mapping)) {
+                html = new Formatter(mapping, {}).format(html);
+            }
+            this.innerHTML = html;
+        }
+    }
+
+    return this;
 }
 
 /**
@@ -1091,49 +1090,49 @@ function initHtmlContent() {
  * @throws {TypeError} value is not an instance of
  */
 function initCSSStylesheet() {
-	if (!(this.shadowRoot instanceof ShadowRoot)) {
-		return this;
-	}
-
-	const styleSheet = this.constructor.getCSSStyleSheet();
-
-	if (styleSheet instanceof CSSStyleSheet) {
-		if (styleSheet.cssRules.length > 0) {
-			this.shadowRoot.adoptedStyleSheets = [styleSheet];
-		}
-	} else if (isArray(styleSheet)) {
-		const assign = [];
-		for (const s of styleSheet) {
-			if (isString(s)) {
-				const trimedStyleSheet = s.trim();
-				if (trimedStyleSheet !== "") {
-					const style = document.createElement("style");
-					style.innerHTML = trimedStyleSheet;
-					this.shadowRoot.prepend(style);
-				}
-				continue;
-			}
-
-			validateInstance(s, CSSStyleSheet);
-
-			if (s.cssRules.length > 0) {
-				assign.push(s);
-			}
-		}
-
-		if (assign.length > 0) {
-			this.shadowRoot.adoptedStyleSheets = assign;
-		}
-	} else if (isString(styleSheet)) {
-		const trimedStyleSheet = styleSheet.trim();
-		if (trimedStyleSheet !== "") {
-			const style = document.createElement("style");
-			style.innerHTML = styleSheet;
-			this.shadowRoot.prepend(style);
-		}
-	}
-
-	return this;
+    if (!(this.shadowRoot instanceof ShadowRoot)) {
+        return this;
+    }
+
+    const styleSheet = this.constructor.getCSSStyleSheet();
+
+    if (styleSheet instanceof CSSStyleSheet) {
+        if (styleSheet.cssRules.length > 0) {
+            this.shadowRoot.adoptedStyleSheets = [styleSheet];
+        }
+    } else if (isArray(styleSheet)) {
+        const assign = [];
+        for (const s of styleSheet) {
+            if (isString(s)) {
+                const trimedStyleSheet = s.trim();
+                if (trimedStyleSheet !== "") {
+                    const style = document.createElement("style");
+                    style.innerHTML = trimedStyleSheet;
+                    this.shadowRoot.prepend(style);
+                }
+                continue;
+            }
+
+            validateInstance(s, CSSStyleSheet);
+
+            if (s.cssRules.length > 0) {
+                assign.push(s);
+            }
+        }
+
+        if (assign.length > 0) {
+            this.shadowRoot.adoptedStyleSheets = assign;
+        }
+    } else if (isString(styleSheet)) {
+        const trimedStyleSheet = styleSheet.trim();
+        if (trimedStyleSheet !== "") {
+            const style = document.createElement("style");
+            style.innerHTML = styleSheet;
+            this.shadowRoot.prepend(style);
+        }
+    }
+
+    return this;
 }
 
 /**
@@ -1145,42 +1144,42 @@ function initCSSStylesheet() {
  * @since 1.8.0
  */
 function initShadowRoot() {
-	let template;
-	let html;
-
-	try {
-		template = findDocumentTemplate(this.constructor.getTag());
-	} catch (e) {
-		html = this.getOption("templates.main", "");
-		if (!isString(html) || html === undefined || html === "") {
-			throw new Error("html is not set.");
-		}
-	}
-
-	this.attachShadow({
-		mode: this.getOption("shadowMode", "open"),
-		delegatesFocus: this.getOption("delegatesFocus", true),
-	});
-
-	if (template instanceof Template) {
-		this.shadowRoot.appendChild(template.createDocumentFragment());
-		return this;
-	}
-
-	const mapping = this.getOption("templateMapping", {});
-	if (isObject(mapping)) {
-		const formatter = new Formatter(mapping);
-		if (this.getOption("templateFormatter.marker.open") !== null) {
-			formatter.setMarker(
-				this.getOption("templateFormatter.marker.open"),
-				this.getOption("templateFormatter.marker.close"),
-			);
-		}
-		html = formatter.format(html);
-	}
-
-	this.shadowRoot.innerHTML = html;
-	return this;
+    let template;
+    let html;
+
+    try {
+        template = findDocumentTemplate(this.constructor.getTag());
+    } catch (e) {
+        html = this.getOption("templates.main", "");
+        if (!isString(html) || html === undefined || html === "") {
+            throw new Error("html is not set.");
+        }
+    }
+
+    this.attachShadow({
+        mode: this.getOption("shadowMode", "open"),
+        delegatesFocus: this.getOption("delegatesFocus", true),
+    });
+
+    if (template instanceof Template) {
+        this.shadowRoot.appendChild(template.createDocumentFragment());
+        return this;
+    }
+
+    const mapping = this.getOption("templateMapping", {});
+    if (isObject(mapping)) {
+        const formatter = new Formatter(mapping);
+        if (this.getOption("templateFormatter.marker.open") !== null) {
+            formatter.setMarker(
+                this.getOption("templateFormatter.marker.open"),
+                this.getOption("templateFormatter.marker.close"),
+            );
+        }
+        html = formatter.format(html);
+    }
+
+    this.shadowRoot.innerHTML = html;
+    return this;
 }
 
 /**
@@ -1194,20 +1193,20 @@ function initShadowRoot() {
  * @throws {DOMException} Failed to execute 'define' on 'CustomElementRegistry': is not a valid custom element name
  */
 function registerCustomElement(element) {
-	validateFunction(element);
-	const customElements = getGlobalObject("customElements");
-	if (customElements === undefined) {
-		throw new Error("customElements is not supported.");
-	}
-
-	const tag = element?.getTag();
-	if (!isString(tag) || tag === "") {
-		throw new Error("tag is not set.");
-	}
-
-	if (customElements.get(tag) !== undefined) {
-		return;
-	}
-
-	customElements.define(tag, element);
+    validateFunction(element);
+    const customElements = getGlobalObject("customElements");
+    if (customElements === undefined) {
+        throw new Error("customElements is not supported.");
+    }
+
+    const tag = element?.getTag();
+    if (!isString(tag) || tag === "") {
+        throw new Error("tag is not set.");
+    }
+
+    if (customElements.get(tag) !== undefined) {
+        return;
+    }
+
+    customElements.define(tag, element);
 }
diff --git a/source/dom/error.mjs b/source/dom/error.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..dd18dfd1a23c77b8b2a86ca7e2763cc0a90be2fd
--- /dev/null
+++ b/source/dom/error.mjs
@@ -0,0 +1,106 @@
+/**
+ * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
+ * Node module: @schukai/monster
+ *
+ * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
+ * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
+ *
+ * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
+ * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
+ * For more information about purchasing a commercial license, please contact schukai GmbH.
+ *
+ * SPDX-License-Identifier: AGPL-3.0
+ */
+
+import {validateInstance, validateString} from "../types/validate.mjs";
+import {ATTRIBUTE_ERRORMESSAGE} from "./constants.mjs";
+
+
+export {addErrorAttribute, removeErrorAttribute};
+
+/**
+ * This method can be used to add an error message to an element.
+ *
+ * @license AGPLv3
+ * @since 3.99.0
+ * @copyright schukai GmbH
+ * @param {HTMLElement} element
+ * @param {string} message
+ * @return {HTMLElement}
+ */
+function addErrorAttribute(element, message) {
+    validateInstance(element, HTMLElement);
+
+    if(message instanceof Error) {
+        message = message.message;
+    }
+
+    if(typeof message !== "string") {
+        if(typeof message === "object" && message !== null) {
+            if(typeof message.toString === "function") {
+                message = message.toString();
+            } else {
+                message = JSON.stringify(message);
+            }
+        } else {
+            message = String(message);
+        }
+    }
+
+    validateString(message);
+
+    if (!element.hasAttribute(ATTRIBUTE_ERRORMESSAGE)) {
+        element.setAttribute(ATTRIBUTE_ERRORMESSAGE, message);
+        return element;
+    }
+
+    const current = element.getAttribute(ATTRIBUTE_ERRORMESSAGE);
+    const list = current.split("::");
+
+    for(let i = 0; i < list.length; i++) {
+        if(list[i] === message) {
+            return element;
+        }
+    }
+
+    list.push(message);
+
+    element.setAttribute(
+        ATTRIBUTE_ERRORMESSAGE,
+        list.join("::")
+    );
+
+    return element;
+}
+
+/**
+ * This method can be used to remove an error message from an element.
+ * @license AGPLv3
+ * @since 3.99.0
+ * @param element
+ * @param message
+ * @param {HTMLElement} element
+ * @param {string} message
+ * @return {HTMLElement}
+ */
+function removeErrorAttribute(element, message) {
+    validateInstance(element, HTMLElement);
+    validateString(message);
+
+    if (!element.hasAttribute(ATTRIBUTE_ERRORMESSAGE)) {
+        return element;
+    }
+
+    const current = element.getAttribute(ATTRIBUTE_ERRORMESSAGE);
+    const list = current.split("::");
+    const newList = list.filter(function (token) {
+        return token !== message;
+    });
+
+    element.setAttribute(
+        ATTRIBUTE_ERRORMESSAGE,
+        newList.join("::")
+    );
+
+    return element;
+}
\ No newline at end of file