diff --git a/packages/streamdown/__tests__/list-item-paragraphs.test.tsx b/packages/streamdown/__tests__/list-item-paragraphs.test.tsx
new file mode 100644
index 00000000..4ba9b5cf
--- /dev/null
+++ b/packages/streamdown/__tests__/list-item-paragraphs.test.tsx
@@ -0,0 +1,23 @@
+import { render } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+import { Streamdown } from "../index";
+
+describe("list item paragraph wrapping", () => {
+ it("does not keep paragraph wrappers for loose list items", () => {
+ const markdown = `
+## Test
+
+- hello
+
+- world
+`;
+ const { container } = render({markdown});
+
+ const items = container.querySelectorAll('[data-streamdown="list-item"]');
+
+ expect(items).toHaveLength(2);
+ for (const item of items) {
+ expect(item.querySelector("p")).toBeNull();
+ }
+ });
+});
diff --git a/packages/streamdown/lib/components.tsx b/packages/streamdown/lib/components.tsx
index 2449ca3a..b27847f9 100644
--- a/packages/streamdown/lib/components.tsx
+++ b/packages/streamdown/lib/components.tsx
@@ -189,13 +189,25 @@ type LiProps = WithNode;
const MemoLi = memo(
({ children, className, node, ...props }: LiProps) => {
const cn = useCn();
+
+ const childArray = Array.isArray(children)
+ ? children.filter((child) => child !== "\n" && child !== "")
+ : [children];
+
+ const normalizedChildren =
+ childArray.length === 1 &&
+ isValidElement(childArray[0]) &&
+ childArray[0].type === "p"
+ ? childArray[0].props.children
+ : children;
+
return (
p]:inline", className)}
data-streamdown="list-item"
{...props}
>
- {children}
+ {normalizedChildren}
);
},