Text
Body text with size and color scales plus a subtle opacity option.
| Prop | Type | Default |
|---|---|---|
| as | "p" | "span" | "li" | "blockquote" | "div" | "p" |
| size | "xl" | "lg" | "md" | "sm" | "xs" | "md" |
| color | "root" | "root-contrast" | "root-100" | "root-100-contrast" | "root-200" | "root-200-contrast" | "primary" | "primary-contrast" | "secondary" | "secondary-contrast" | "accent" | "accent-contrast" | "root-contrast" |
| subtle | boolean | false |
| weight | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | — |
Text xl — intro/hero copy
Text lg — large body
Text md — standard paragraph
Text sm — captions, fine print
Text xs — labels, metadata
<Text size="xl">Text xl — intro/hero copy</Text>
<Text size="lg">Text lg — large body</Text>
<Text size="md">Text md — standard paragraph</Text>
<Text size="sm">Text sm — captions, fine print</Text>
<Text size="xs">Text xs — labels, metadata</Text>root-contrast
root-contrast subtle
primary
secondary
accent
<Text color="root-contrast">root-contrast</Text>
<Text color="root-contrast" subtle>root-contrast subtle</Text>
<Text color="primary">primary</Text>
<Text color="secondary">secondary</Text>
<Text color="accent">accent</Text>Normal text
Subtle text — softened with opacity
<Text>Normal text</Text>
<Text subtle>Subtle text — softened with opacity</Text>import { Text } from "@/ui";
<Text>Hello world</Text>src/ui/Text.tsx
import { cn } from "@/utils";
type TextColor =
| "root"
| "root-contrast"
| "root-100"
| "root-100-contrast"
| "root-200"
| "root-200-contrast"
| "primary"
| "primary-contrast"
| "secondary"
| "secondary-contrast";
type TextSize = "xl" | "lg" | "md" | "sm" | "xs";
type TextElement = "p" | "span" | "li" | "blockquote" | "div";
type FontWeight = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
const weightClass: Record<FontWeight, string> = {
100: "font-thin",
200: "font-extralight",
300: "font-light",
400: "font-normal",
500: "font-medium",
600: "font-semibold",
700: "font-bold",
800: "font-extrabold",
900: "font-black",
};
const sizeClass: Record<TextSize, string> = {
xl: "text-2xl font-light leading-relaxed text-balance sm:text-3xl",
lg: "text-lg leading-relaxed text-pretty",
md: "text-base leading-relaxed text-pretty",
sm: "text-sm leading-relaxed text-pretty",
xs: "text-xs leading-relaxed text-pretty",
};
const colorClass: Record<string, string> = {
root: "text-root",
"root-contrast": "text-root-contrast",
"root-subtle": "text-root-subtle",
"root-contrast-subtle": "text-root-contrast-subtle",
"root-100": "text-root-100",
"root-100-contrast": "text-root-100-contrast",
"root-100-subtle": "text-root-100-subtle",
"root-100-contrast-subtle": "text-root-100-contrast-subtle",
"root-200": "text-root-200",
"root-200-contrast": "text-root-200-contrast",
"root-200-subtle": "text-root-200-subtle",
"root-200-contrast-subtle": "text-root-200-contrast-subtle",
primary: "text-primary",
"primary-contrast": "text-primary-contrast",
"primary-subtle": "text-primary-subtle",
"primary-contrast-subtle": "text-primary-contrast-subtle",
secondary: "text-secondary",
"secondary-contrast": "text-secondary-contrast",
"secondary-subtle": "text-secondary-subtle",
"secondary-contrast-subtle": "text-secondary-contrast-subtle",
};
interface TextProps extends Omit<React.HTMLAttributes<HTMLElement>, "color"> {
as?: TextElement;
size?: TextSize;
color?: TextColor;
subtle?: boolean;
weight?: FontWeight;
}
export function Text({
as: Tag = "p",
size = "md",
color = "root-contrast",
subtle = false,
weight,
className,
children,
...props
}: TextProps) {
const key = subtle ? `${color}-subtle` : color;
return (
<Tag
className={cn(
sizeClass[size],
colorClass[key],
weight && weightClass[weight],
className,
)}
{...props}
>
{children}
</Tag>
);
}