diff --git a/.changeset/preserve-markdown-table-literals.md b/.changeset/preserve-markdown-table-literals.md
new file mode 100644
index 00000000..73ce3003
--- /dev/null
+++ b/.changeset/preserve-markdown-table-literals.md
@@ -0,0 +1,5 @@
+---
+"streamdown": patch
+---
+
+Preserve literal HTML tags and character entities when copying or downloading tables as Markdown.
diff --git a/packages/streamdown/__tests__/table-utils.test.ts b/packages/streamdown/__tests__/table-utils.test.ts
index b0c1e454..e82ac0aa 100644
--- a/packages/streamdown/__tests__/table-utils.test.ts
+++ b/packages/streamdown/__tests__/table-utils.test.ts
@@ -1,3 +1,4 @@
+import { marked } from "marked";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
escapeMarkdownTableCell,
@@ -481,6 +482,26 @@ describe("Table Utils", () => {
});
describe("tableDataToMarkdown", () => {
+ it("should preserve literal HTML and entities when rendered again", () => {
+ const table = document.createElement("table");
+ table.innerHTML = `
+ | Type <T> | Entities |
+
+ Array<string> |
+ © and | |
+
+ `;
+ const data = extractTableDataFromElement(table);
+ const markdown = tableDataToMarkdown(data);
+ const container = document.createElement("div");
+ container.innerHTML = marked.parse(markdown, { async: false });
+ const restoredTable = container.querySelector(
+ "table"
+ ) as HTMLTableElement;
+
+ expect(extractTableDataFromElement(restoredTable)).toEqual(data);
+ });
+
it("should convert simple table data to Markdown", () => {
const data: TableData = {
headers: ["Name", "Age", "City"],
diff --git a/packages/streamdown/lib/table/utils.ts b/packages/streamdown/lib/table/utils.ts
index aece0df5..05475940 100644
--- a/packages/streamdown/lib/table/utils.ts
+++ b/packages/streamdown/lib/table/utils.ts
@@ -184,7 +184,14 @@ export const escapeMarkdownTableCell = (cell: string): string => {
// OPTIMIZATION: Fast path for cells that don't need escaping - check chars directly
let needsEscaping = false;
for (const char of cell) {
- if (char === "\\" || char === "|" || char === "\n") {
+ if (
+ char === "\\" ||
+ char === "|" ||
+ char === "\n" ||
+ char === "&" ||
+ char === "<" ||
+ char === ">"
+ ) {
needsEscaping = true;
break;
}
@@ -202,6 +209,12 @@ export const escapeMarkdownTableCell = (cell: string): string => {
parts.push("\\|");
} else if (char === "\n") {
parts.push("
");
+ } else if (char === "&") {
+ parts.push("&");
+ } else if (char === "<") {
+ parts.push("<");
+ } else if (char === ">") {
+ parts.push(">");
} else {
parts.push(char);
}