diff --git a/change/@microsoft-fast-element-ccc364c3-3d9a-4477-a877-812d7fc72bff.json b/change/@microsoft-fast-element-ccc364c3-3d9a-4477-a877-812d7fc72bff.json
new file mode 100644
index 00000000000..a5c35292f03
--- /dev/null
+++ b/change/@microsoft-fast-element-ccc364c3-3d9a-4477-a877-812d7fc72bff.json
@@ -0,0 +1,7 @@
+{
+ "type": "patch",
+ "comment": "Address hydration mismatches when a template is provided but no boundaries exist, this will skip creation of a new view.",
+ "packageName": "@microsoft/fast-element",
+ "email": "hello@mohamedmansour.com",
+ "dependentChangeType": "none"
+}
diff --git a/change/@microsoft-fast-html-1cbbfa63-0099-407f-9cd2-36fe3f44f3c3.json b/change/@microsoft-fast-html-1cbbfa63-0099-407f-9cd2-36fe3f44f3c3.json
new file mode 100644
index 00000000000..f49387e66c7
--- /dev/null
+++ b/change/@microsoft-fast-html-1cbbfa63-0099-407f-9cd2-36fe3f44f3c3.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "Address hydration mismatches when a template is provided but no boundaries exist, this will skip creation of a new view.",
+ "packageName": "@microsoft/fast-html",
+ "email": "hello@mohamedmansour.com",
+ "dependentChangeType": "none"
+}
diff --git a/packages/fast-element/src/templating/html-binding-directive.ts b/packages/fast-element/src/templating/html-binding-directive.ts
index 48d466624db..8120ebd1eb4 100644
--- a/packages/fast-element/src/templating/html-binding-directive.ts
+++ b/packages/fast-element/src/templating/html-binding-directive.ts
@@ -106,6 +106,18 @@ function updateContent(
// If the value has a "create" method, then it's a ContentTemplate.
if (isContentTemplate(value)) {
+ // During hydration, if a template is provided but no view boundaries
+ // exist and the target text node is empty, the server did not render
+ // this content. Skip creating a new view to avoid a hydration mismatch.
+ if (
+ isHydratable(controller) &&
+ controller.hydrationStage !== HydrationStage.hydrated &&
+ controller.bindingViewBoundaries[this.targetNodeId] === undefined &&
+ !target.nodeValue
+ ) {
+ return;
+ }
+
target.textContent = "";
let view = target.$fastView as ComposableView;
diff --git a/packages/fast-html/src/components/utilities.ts b/packages/fast-html/src/components/utilities.ts
index edadc85d941..d6caae89dbb 100644
--- a/packages/fast-html/src/components/utilities.ts
+++ b/packages/fast-html/src/components/utilities.ts
@@ -1033,7 +1033,43 @@ export function resolveWhen(
level,
schema
);
- return (x: boolean, c: any) => binding(x, c);
+
+ // Raw value resolver for the expression's primary property path.
+ // Used during hydration to distinguish "property doesn't exist on
+ // the client" (undefined, server-only) from "property is explicitly falsy."
+ const rawBinding = !expression.expression.leftIsValue
+ ? pathResolver(
+ expression.expression.left as string,
+ parentContext,
+ level,
+ schema.getSchema(rootPropertyName as string) as JSONSchema
+ )
+ : null;
+
+ let hydrationDone = false;
+ return (x: boolean, c: any) => {
+ const result = binding(x, c);
+ if (result) return result;
+
+ // During hydration, trust the server-rendered state only when the
+ // condition references a property not defined on the client element
+ // (raw value is undefined). Return true so the inner template is
+ // hydrated and its bindings (event listeners, etc.) are properly
+ // attached to the existing DOM.
+ // When the property IS defined but explicitly falsy (e.g. false, 0),
+ // respect the client value to avoid a hydration mismatch.
+ if (!hydrationDone) {
+ if (c?.hydrationStage === "hydrated") {
+ hydrationDone = true;
+ } else if (c?.hydrationStage && rawBinding) {
+ const rawValue = rawBinding(x, c);
+ if (rawValue === undefined) {
+ return true;
+ }
+ }
+ }
+ return result;
+ };
}
type DataType = "array" | "object" | "primitive";
diff --git a/packages/fast-html/test/fixtures/when-event/index.html b/packages/fast-html/test/fixtures/when-event/index.html
new file mode 100644
index 00000000000..04f48a9d612
--- /dev/null
+++ b/packages/fast-html/test/fixtures/when-event/index.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ anything, really
+
+
+ anything, really
+
+
+
diff --git a/packages/fast-html/test/fixtures/when-event/main.ts b/packages/fast-html/test/fixtures/when-event/main.ts
new file mode 100644
index 00000000000..3eef1eedc88
--- /dev/null
+++ b/packages/fast-html/test/fixtures/when-event/main.ts
@@ -0,0 +1,34 @@
+import { RenderableFASTElement, TemplateElement } from "@microsoft/fast-html";
+import { FASTElement, observable } from "@microsoft/fast-element";
+
+// This element intentionally does NOT define "serverOnly" as a property.
+// The f-when condition references "serverOnly" which only exists in server state,
+// simulating the real-world scenario where uses server-only data.
+class TestElement extends FASTElement {
+ public clickCount: number = 0;
+
+ public handleClick = (): void => {
+ this.clickCount++;
+ console.log("clicked:" + this.clickCount);
+ };
+}
+RenderableFASTElement(TestElement).defineAsync({
+ name: "test-element",
+ templateOptions: "defer-and-hydrate",
+});
+
+// This element explicitly defines "someprop" as false.
+// The server rendered with someprop=true (from JSON), so SSR content is present.
+// During hydration, the client value is false, which should be respected.
+class TestElementFalse extends FASTElement {
+ @observable
+ someprop: boolean = false;
+}
+RenderableFASTElement(TestElementFalse).defineAsync({
+ name: "test-element-false",
+ templateOptions: "defer-and-hydrate",
+});
+
+TemplateElement.define({
+ name: "f-template",
+});
diff --git a/packages/fast-html/test/fixtures/when-event/when-event.html b/packages/fast-html/test/fixtures/when-event/when-event.html
new file mode 100644
index 00000000000..60a51f65aa8
--- /dev/null
+++ b/packages/fast-html/test/fixtures/when-event/when-event.html
@@ -0,0 +1 @@
+
diff --git a/packages/fast-html/test/fixtures/when-event/when-event.json b/packages/fast-html/test/fixtures/when-event/when-event.json
new file mode 100644
index 00000000000..c85a2e1c39d
--- /dev/null
+++ b/packages/fast-html/test/fixtures/when-event/when-event.json
@@ -0,0 +1,3 @@
+{
+ "serverOnly": true
+}
diff --git a/packages/fast-html/test/fixtures/when-event/when-event.spec.ts b/packages/fast-html/test/fixtures/when-event/when-event.spec.ts
new file mode 100644
index 00000000000..93c2e859e8b
--- /dev/null
+++ b/packages/fast-html/test/fixtures/when-event/when-event.spec.ts
@@ -0,0 +1,52 @@
+import { expect, test } from "@playwright/test";
+
+test.describe("f-when with event binding", async () => {
+ test("event binding inside f-when should fire after hydration", async ({ page }) => {
+ await page.goto("/fixtures/when-event/");
+
+ const customElement = page.locator("#when-event-show");
+
+ // Button should be visible (SSR rendered, condition true)
+ const button = customElement.locator("button");
+ await expect(button).toHaveText("Click me");
+
+ // Click the button - event binding should work
+ await button.click();
+
+ // Verify the click handler fired
+ await expect(customElement).toHaveJSProperty("clickCount", 1);
+
+ // Click again to confirm repeated clicks work
+ await button.click();
+ await expect(customElement).toHaveJSProperty("clickCount", 2);
+ });
+
+ test("f-when with false condition should not create content during hydration", async ({ page }) => {
+ await page.goto("/fixtures/when-event/");
+
+ const customElement = page.locator("#when-event-hide");
+
+ // No button should exist (SSR condition was false)
+ const button = customElement.locator("button");
+ await expect(button).toHaveCount(0);
+
+ // Element should hydrate without errors
+ await expect(customElement).toHaveJSProperty("clickCount", 0);
+ });
+
+ test("should respect client-side false value even when server rendered content", async ({
+ page,
+ }) => {
+ await page.goto("/fixtures/when-event/");
+
+ const element = page.locator("#false-prop");
+
+ // The property is explicitly false on the client element class
+ await expect(element).toHaveJSProperty("someprop", false);
+
+ // After hydration, content should NOT be visible because someprop is false.
+ // The server rendered "anything, really" because it had someprop=true,
+ // but the client value is false, so the content should be removed.
+ await expect(element).not.toHaveText("anything, really");
+ });
+});