diff --git a/packages/kumo-docs-astro/src/pages/components/text.mdx b/packages/kumo-docs-astro/src/pages/components/text.mdx
index 3e6d1e36d9..67ca21dda7 100644
--- a/packages/kumo-docs-astro/src/pages/components/text.mdx
+++ b/packages/kumo-docs-astro/src/pages/components/text.mdx
@@ -9,6 +9,7 @@ import ComponentExample from "~/components/docs/ComponentExample.astro";
import ComponentSection from "~/components/docs/ComponentSection.astro";
import Heading from "~/components/docs/Heading.astro";
import PropsTable from "~/components/docs/PropsTable.astro";
+import { Text } from "@cloudflare/kumo";
import {
TextVariantsDemo,
TextTruncateDemo,
@@ -54,10 +55,33 @@ export default function Example() {
```
- Restrictions
-
+ Semantic HTML
+
+ The `variant` prop controls visual styling only—it does not determine the HTML element rendered.
+ Use the `as` prop to set the appropriate semantic element for your document outline:
+
+
+```tsx
+// Heading variants render as by default
+Styled as h1, but renders a span
+
+// Use `as` to set semantic heading levels
+Page Title
+Section Title
+Visually large, but semantically h3
+```
+
+
+ The `as` prop accepts: `"h1"` through `"h6"`, `"p"`, and `"span"`.
+ Body variants default to `"p"`, while heading and mono variants default to `"span"`.
+
+
+
+
+ Restrictions
+
The `bold` and `size` props are intentionally restricted to the `base`, `secondary`, `success`, and `error` text variants.
-
+
```tsx
Body
@@ -66,10 +90,10 @@ export default function Example() {
Error
```
-
+
Monospace variants (`mono` and `mono-secondary`) can only set `size` to `lg`
and cannot use the `bold` prop:
-
+
```tsx
Monospace
@@ -77,12 +101,13 @@ export default function Example() {
Monospace // Doesn't compile
```
-
- Headings (i.e. `h1`, `h2` and `h3` variants) cannot use these props at all:
-
+
+ Headings (i.e. `heading1`, `heading2` and `heading3` variants) cannot use
+ these props at all:
+
```tsx
-
+
Heading 1
// Doesn't compile
```
@@ -95,9 +120,9 @@ export default function Example() {
Truncate
-
+
Use the `truncate` prop to clip overflowing text with an ellipsis. This adds `truncate min-w-0` classes, which is useful when `Text` is inside a flex or grid container.
-
+
diff --git a/packages/kumo/src/components/text/text.tsx b/packages/kumo/src/components/text/text.tsx
index 41dfa0a4de..6d110b6f74 100644
--- a/packages/kumo/src/components/text/text.tsx
+++ b/packages/kumo/src/components/text/text.tsx
@@ -5,7 +5,6 @@ import {
type ForwardedRef,
forwardRef,
useMemo,
- type ElementType,
} from "react";
import { cn } from "../../utils/cn";
@@ -140,13 +139,24 @@ type Monospace = "mono" | "mono-secondary";
type TextSize = KumoTextSize;
type TextVariant = KumoTextVariant;
+/** Valid HTML elements for the Text component's `as` prop. */
+export type TextElement =
+ | "h1"
+ | "h2"
+ | "h3"
+ | "h4"
+ | "h5"
+ | "h6"
+ | "p"
+ | "span";
+
type BaseTextProps = Omit<
ComponentPropsWithoutRef<"span">,
"className" | "style"
> & {
DANGEROUS_className?: string;
DANGEROUS_style?: CSSProperties;
- as?: ElementType;
+ as?: TextElement;
};
type TextPropsInternal = BaseTextProps &
@@ -213,20 +223,21 @@ export interface TextProps {
bold?: boolean;
/** Whether to truncate overflowing text with an ellipsis. Adds `truncate min-w-0` classes. */
truncate?: boolean;
- /** The HTML element type to render as (e.g. `"span"`, `"p"`, `"h1"`). Auto-selected based on variant if omitted. */
- as?: ElementType;
+ /** The HTML element to render (`"h1"`-`"h6"`, `"p"`, or `"span"`). Defaults to `"p"` for body variants and `"span"` for headings/mono. Use this to set semantic heading levels. */
+ as?: TextElement;
/** Text content. */
children?: React.ReactNode;
}
/**
* Typography component for rendering text with consistent styling.
- * Automatically selects the appropriate HTML element based on variant
- * (`h1`/`h2`/`h3` for headings, `p` for body, `span` for mono).
+ * Renders as `` for body variants and `` for headings/mono.
+ * Use the `as` prop to set semantic HTML elements for proper document outlines.
*
* @example
* ```tsx
- * Dashboard
+ * Page Title
+ * Section Title
* Default body text
* ```
*/
@@ -247,12 +258,14 @@ function _Text(
const isCopy = ["body", "secondary", "success", "error"].includes(variant);
const isMono = ["mono", "mono-secondary"].includes(variant);
+ // Heading variants no longer auto-select h1/h2/h3 to avoid coupling visual
+ // presentation to semantic HTML. Use the `as` prop to set the appropriate
+ // heading level for your document outline (e.g., as="h2").
const Component = useMemo(() => {
if (as) return as;
- if (variant === "heading1") return "h1";
- if (variant === "heading2") return "h2";
- if (variant === "heading3") return "h3";
if (["mono", "mono-secondary"].includes(variant)) return "span";
+ // Headings and body text default to span; use `as` for semantic elements
+ if (["heading1", "heading2", "heading3"].includes(variant)) return "span";
return "p";
}, [variant, as]);