diff --git a/source/components/form/select.mjs b/source/components/form/select.mjs
index f9264f199fd57bbb0d0791d30f862076f3f6bcf4..7c690772b013c8308a242c5bef9f02748277021d 100644
--- a/source/components/form/select.mjs
+++ b/source/components/form/select.mjs
@@ -264,8 +264,8 @@ const FILTER_POSITION_INLINE = "inline";
 /**
  * A select control that can be used to select o
  *
- * @issue @issue https://localhost.alvine.dev:8444/development/issues/closed/280.html
- * @issue @issue https://localhost.alvine.dev:8444/development/issues/closed/287.html
+ * @issue @issue https://localhost.alvine.dev:8440/development/issues/closed/280.html
+ * @issue @issue https://localhost.alvine.dev:8440/development/issues/closed/287.html
  *
  * @fragments /fragments/components/form/select/
  *
@@ -520,7 +520,11 @@ class Select extends CustomControl {
 
         if (self.hasAttribute("value")) {
             new Processing(10, () => {
-                this.value = this.getAttribute("value");
+                const oldValue = self.value;
+                const newValue = self.getAttribute("value");
+                if (oldValue !== newValue) {
+                    self.value = newValue;
+                }
             })
                 .run()
                 .catch((e) => {
diff --git a/source/dom/customcontrol.mjs b/source/dom/customcontrol.mjs
index fc68feb3c358193e722166ee6d4df2cb00730526..3b58b6e54287978c2a497c907636e916ee467c3e 100644
--- a/source/dom/customcontrol.mjs
+++ b/source/dom/customcontrol.mjs
@@ -17,6 +17,8 @@ import { addAttributeToken } from "./attributes.mjs";
 import { ATTRIBUTE_ERRORMESSAGE } from "./constants.mjs";
 import { CustomElement, attributeObserverSymbol } from "./customelement.mjs";
 import { instanceSymbol } from "../constants.mjs";
+import {DeadMansSwitch} from "../util/deadmansswitch.mjs";
+import {addErrorAttribute} from "./error.mjs";
 
 export { CustomControl };
 
@@ -77,8 +79,8 @@ class CustomControl extends CustomElement {
 			);
 		}
 
-		// initialize a MutationObserver to watch for attribute changes
-		initObserver.call(this);
+		// watch for attribute value changes
+		initValueAttributeObserver.call(this);
 	}
 
 	/**
@@ -348,14 +350,43 @@ function getInternal() {
 	return this[attachedInternalSymbol];
 }
 
+const debounceValueSymbol = Symbol("debounceValue");
+
 /**
+ *
+ * @issue https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/290
+ *
  * @private
  * @return {object}
  * @this CustomControl
  */
-function initObserver() {
-	// value
+function initValueAttributeObserver() {
+	const self = this;
+
 	this[attributeObserverSymbol]["value"] = () => {
-		this.setOption("value", this.getAttribute("value"));
+
+		if (self[debounceValueSymbol] instanceof DeadMansSwitch) {
+			try {
+				self[debounceValueSymbol].touch();
+				return;
+			} catch (e) {
+				// catch Error("has already run");
+				if (e.message !== "has already run") {
+					throw e;
+				}
+				delete self[debounceValueSymbol];
+			}
+		}
+
+		self[debounceValueSymbol] = new DeadMansSwitch(50, () => {
+			requestAnimationFrame(() => {
+				const oldValue = self.getAttribute("value");
+				const newValue = self.getOption("value");
+
+				if (oldValue !== newValue) {
+					this.setOption("value", this.getAttribute("value"));
+				}
+			});
+		});
 	};
 }
diff --git a/source/dom/customelement.mjs b/source/dom/customelement.mjs
index b6b344a3c714832f0c0e22187d74515b3ea8224f..e24a38c4495b5cdad83d7ae92329d6196b5a854b 100644
--- a/source/dom/customelement.mjs
+++ b/source/dom/customelement.mjs
@@ -687,6 +687,7 @@ class CustomElement extends HTMLElement {
 	 * @since 1.15.0
 	 */
 	attributeChangedCallback(attrName, oldVal, newVal) {
+
 		if (attrName.startsWith("data-monster-option-")) {
 			setOptionFromAttribute(
 				this,