diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/error.txt b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/error.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/expected.html b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/expected.html
new file mode 100644
index 0000000000..7ff2494fe4
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/expected.html
@@ -0,0 +1,8 @@
+<x-parent>
+  <template shadowrootmode="open">
+    <x-child class="button__icon">
+      <template shadowrootmode="open">
+      </template>
+    </x-child>
+  </template>
+</x-parent>
\ No newline at end of file
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/index.js b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/index.js
new file mode 100644
index 0000000000..d5a55ceefa
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/index.js
@@ -0,0 +1,3 @@
+export const tagName = 'x-parent';
+export { default } from 'x/parent';
+export * from 'x/parent';
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/modules/x/child/child.html b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/modules/x/child/child.html
new file mode 100644
index 0000000000..41a40c8d47
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/modules/x/child/child.html
@@ -0,0 +1 @@
+<template></template>
\ No newline at end of file
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/modules/x/child/child.js b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/modules/x/child/child.js
new file mode 100644
index 0000000000..686f791240
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/modules/x/child/child.js
@@ -0,0 +1,5 @@
+import { LightningElement } from 'lwc';
+
+export default class extends LightningElement {
+
+}
\ No newline at end of file
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/modules/x/parent/parent.html b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/modules/x/parent/parent.html
new file mode 100644
index 0000000000..35f782c7f9
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/modules/x/parent/parent.html
@@ -0,0 +1,3 @@
+<template>
+    <x-child class={computedClassNames}></x-child>
+</template>
\ No newline at end of file
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/modules/x/parent/parent.js b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/modules/x/parent/parent.js
new file mode 100644
index 0000000000..be898faba8
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/child/modules/x/parent/parent.js
@@ -0,0 +1,9 @@
+import { LightningElement } from 'lwc';
+
+export default class CustomButton extends LightningElement {
+    get computedClassNames() {
+      return {
+        button__icon: true
+      }
+    }
+  }
\ No newline at end of file
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/error.txt b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/error.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/expected.html b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/expected.html
new file mode 100644
index 0000000000..7ff2494fe4
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/expected.html
@@ -0,0 +1,8 @@
+<x-parent>
+  <template shadowrootmode="open">
+    <x-child class="button__icon">
+      <template shadowrootmode="open">
+      </template>
+    </x-child>
+  </template>
+</x-parent>
\ No newline at end of file
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/index.js b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/index.js
new file mode 100644
index 0000000000..d5a55ceefa
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/index.js
@@ -0,0 +1,3 @@
+export const tagName = 'x-parent';
+export { default } from 'x/parent';
+export * from 'x/parent';
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/modules/x/child/child.html b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/modules/x/child/child.html
new file mode 100644
index 0000000000..41a40c8d47
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/modules/x/child/child.html
@@ -0,0 +1 @@
+<template></template>
\ No newline at end of file
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/modules/x/child/child.js b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/modules/x/child/child.js
new file mode 100644
index 0000000000..686f791240
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/modules/x/child/child.js
@@ -0,0 +1,5 @@
+import { LightningElement } from 'lwc';
+
+export default class extends LightningElement {
+
+}
\ No newline at end of file
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/modules/x/parent/parent.html b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/modules/x/parent/parent.html
new file mode 100644
index 0000000000..2d083bdcee
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/modules/x/parent/parent.html
@@ -0,0 +1,3 @@
+<template>
+    <lwc:component lwc:is={ctor} class={computedClassNames}></lwc:component>
+</template>
\ No newline at end of file
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/modules/x/parent/parent.js b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/modules/x/parent/parent.js
new file mode 100644
index 0000000000..de6bae6ade
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-class/unstyled/component/modules/x/parent/parent.js
@@ -0,0 +1,11 @@
+import { LightningElement } from 'lwc';
+import xChild from 'x/child';
+
+export default class CustomButton extends LightningElement {
+  ctor = xChild;
+    get computedClassNames() {
+      return {
+        button__icon: true
+      }
+    }
+  }
\ No newline at end of file
diff --git a/packages/@lwc/ssr-compiler/src/compile-template/shared.ts b/packages/@lwc/ssr-compiler/src/compile-template/shared.ts
index fba2889edd..be1fa9509d 100644
--- a/packages/@lwc/ssr-compiler/src/compile-template/shared.ts
+++ b/packages/@lwc/ssr-compiler/src/compile-template/shared.ts
@@ -129,7 +129,13 @@ export function getChildAttrsOrProps(
 
                 return b.property('init', key, b.literal(type === 'Attribute' ? '' : value.value));
             } else if (value.type === 'Identifier' || value.type === 'MemberExpression') {
-                const propValue = expressionIrToEs(value, cxt);
+                let propValue = expressionIrToEs(value, cxt);
+
+                if (name === 'class') {
+                    cxt.import('normalizeClass');
+                    propValue = b.callExpression(b.identifier('normalizeClass'), [propValue]);
+                }
+
                 return b.property('init', key, propValue);
             }
             throw new Error(`Unimplemented child attr IR node type: ${value.type}`);