diff --git a/.changeset/silent-production-text-warnings.md b/.changeset/silent-production-text-warnings.md
new file mode 100644
index 0000000000..3e164b2460
--- /dev/null
+++ b/.changeset/silent-production-text-warnings.md
@@ -0,0 +1,5 @@
+---
+"@cloudflare/kumo": patch
+---
+
+Suppress deprecated `Text` heading variant warnings in production while retaining them during development.
diff --git a/packages/kumo/src/components/text/text.test.tsx b/packages/kumo/src/components/text/text.test.tsx
index e1121c3f09..edfea6fd20 100644
--- a/packages/kumo/src/components/text/text.test.tsx
+++ b/packages/kumo/src/components/text/text.test.tsx
@@ -1,8 +1,13 @@
-import { describe, expect, it, vi } from "vite-plus/test";
+import { afterEach, describe, expect, it, vi } from "vite-plus/test";
import { render } from "@testing-library/react";
import { Text, textVariants } from "./text";
describe("Text", () => {
+ afterEach(() => {
+ vi.unstubAllEnvs();
+ vi.restoreAllMocks();
+ });
+
it("renders heading as a 16px semibold span by default", () => {
const { container } = render(Heading);
const heading = container.querySelector("span");
@@ -26,20 +31,39 @@ describe("Text", () => {
expect(heading?.classList.contains("font-semibold")).toBe(true);
});
- it("warns when a deprecated heading variant is used", () => {
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
-
- render(
-
- Legacy heading
- ,
- );
-
- expect(warn).toHaveBeenCalledWith(
- expect.stringContaining('variant="heading1" is deprecated'),
- );
- warn.mockRestore();
- });
+ it.each(["heading1", "heading2", "heading3"] as const)(
+ "warns in development when deprecated variant %s is used",
+ (variant) => {
+ vi.stubEnv("NODE_ENV", "development");
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
+
+ render(
+
+ Legacy heading
+ ,
+ );
+
+ expect(warn).toHaveBeenCalledWith(
+ expect.stringContaining(`variant="${variant}" is deprecated`),
+ );
+ },
+ );
+
+ it.each(["heading1", "heading2", "heading3"] as const)(
+ "does not warn in production when deprecated variant %s is used",
+ (variant) => {
+ vi.stubEnv("NODE_ENV", "production");
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
+
+ render(
+
+ Legacy heading
+ ,
+ );
+
+ expect(warn).not.toHaveBeenCalled();
+ },
+ );
it("renders body variant as
by default", () => {
const { container } = render(Body copy);
diff --git a/packages/kumo/tests/build/production-behavior.test.ts b/packages/kumo/tests/build/production-behavior.test.ts
new file mode 100644
index 0000000000..ba3f829279
--- /dev/null
+++ b/packages/kumo/tests/build/production-behavior.test.ts
@@ -0,0 +1,28 @@
+import { createElement } from "react";
+import { render } from "@testing-library/react";
+import { describe, expect, it, vi } from "vite-plus/test";
+import { existsSync } from "fs";
+import { dirname, join } from "path";
+import { fileURLToPath } from "url";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const textEntryPath = join(__dirname, "../../dist/components/text.js");
+const isBuilt = existsSync(textEntryPath);
+
+describe.skipIf(!isBuilt)("Production behavior (Post-Build)", () => {
+ it("does not emit deprecated Text variant warnings", async () => {
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
+ const { Text } = await import("../../dist/components/text.js");
+
+ render(
+ createElement(Text, {
+ variant: "heading1",
+ as: "h1",
+ children: "Legacy heading",
+ }),
+ );
+
+ expect(warn).not.toHaveBeenCalled();
+ warn.mockRestore();
+ });
+});
diff --git a/packages/kumo/vite.config.ts b/packages/kumo/vite.config.ts
index a6c76123c1..15fef8ba53 100644
--- a/packages/kumo/vite.config.ts
+++ b/packages/kumo/vite.config.ts
@@ -196,6 +196,9 @@ export default defineConfig({
platform: "browser",
outDir: "dist",
dts: false,
+ // Published artifacts are production builds. Define NODE_ENV explicitly
+ // so Rolldown removes development-only warnings and their message strings.
+ define: { "process.env.NODE_ENV": '"production"' },
// The dts pass emits declarations this pass can't see — pair them by
// co-location; asset entries aren't chunks, so re-attach them here.
exports: {