diff --git a/apps/oxfmt/test/cli/embedded_languages/__snapshots__/embedded_languages.test.ts.snap b/apps/oxfmt/test/cli/embedded_languages/__snapshots__/embedded_languages.test.ts.snap
index 3b74e81fb0a5b..2b9cdc6bf2095 100644
--- a/apps/oxfmt/test/cli/embedded_languages/__snapshots__/embedded_languages.test.ts.snap
+++ b/apps/oxfmt/test/cli/embedded_languages/__snapshots__/embedded_languages.test.ts.snap
@@ -16,6 +16,27 @@ const mixedTemplate = html\`
Title
\`;
const mixedDocs = md\`#Documentation
This is **important**.\`;
+// Multi-line with blank lines - should preserve blank lines without trailing whitespace
+const multilineCSS = css\`
+ .foo {
+ color: red;
+ }
+
+ .bar {
+ color: blue;
+ }
+\`;
+
+const multilineGQL = gql\`
+ type Foo {
+ name: String!
+ }
+
+ type Bar {
+ value: Int!
+ }
+\`;
+
// Empty - Regular template literals retain newlines and spaces, but embedded ones are condensed
const empty = css\`\`;
const empty2 = styled\`
@@ -58,6 +79,27 @@ const mixedDocs = md\`
This is **important**.
\`;
+// Multi-line with blank lines - should preserve blank lines without trailing whitespace
+const multilineCSS = css\`
+ .foo {
+ color: red;
+ }
+
+ .bar {
+ color: blue;
+ }
+\`;
+
+const multilineGQL = gql\`
+ type Foo {
+ name: String!
+ }
+
+ type Bar {
+ value: Int!
+ }
+\`;
+
// Empty - Regular template literals retain newlines and spaces, but embedded ones are condensed
const empty = css\`\`;
const empty2 = styled\`\`;
diff --git a/apps/oxfmt/test/cli/embedded_languages/fixtures/mixed.js b/apps/oxfmt/test/cli/embedded_languages/fixtures/mixed.js
index 1c5ec4ef9df01..1af5931bc4d6a 100644
--- a/apps/oxfmt/test/cli/embedded_languages/fixtures/mixed.js
+++ b/apps/oxfmt/test/cli/embedded_languages/fixtures/mixed.js
@@ -10,6 +10,27 @@ const mixedTemplate = html`Title
`;
const mixedDocs = md`#Documentation
This is **important**.`;
+// Multi-line with blank lines - should preserve blank lines without trailing whitespace
+const multilineCSS = css`
+ .foo {
+ color: red;
+ }
+
+ .bar {
+ color: blue;
+ }
+`;
+
+const multilineGQL = gql`
+ type Foo {
+ name: String!
+ }
+
+ type Bar {
+ value: Int!
+ }
+`;
+
// Empty - Regular template literals retain newlines and spaces, but embedded ones are condensed
const empty = css``;
const empty2 = styled`
diff --git a/crates/oxc_formatter/src/print/template.rs b/crates/oxc_formatter/src/print/template.rs
index fcc7d2ee4dad9..374c49a9709e5 100644
--- a/crates/oxc_formatter/src/print/template.rs
+++ b/crates/oxc_formatter/src/print/template.rs
@@ -822,7 +822,11 @@ fn format_embedded_template<'a>(
let format_content = format_with(|f: &mut Formatter<'_, 'a>| {
let content = f.context().allocator().alloc_str(&formatted);
for line in content.split('\n') {
- write!(f, [text(line), hard_line_break()]);
+ if line.is_empty() {
+ write!(f, [empty_line()]);
+ } else {
+ write!(f, [text(line), hard_line_break()]);
+ }
}
});