Text

Body text with size and color scales plus a subtle opacity option.

API Reference

PropTypeDefault
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"
subtlebooleanfalse
weight100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900

Sizes

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>

Colors

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>

Subtle

Normal text

Subtle text — softened with opacity

<Text>Normal text</Text>
<Text subtle>Subtle text — softened with opacity</Text>

Basic Usage

import { Text } from "@/ui";

<Text>Hello world</Text>

Source

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>
  );
}