Loading…
Loading…
A flexible typography component for body text, captions, and other content.
The Text component is the typographic workhorse of any design system — it renders body copy, captions, labels, helper text, and every other piece of non-heading content with consistent sizing, spacing, and color. While the Heading component handles structural section titles, the Text component handles everything else.
Text might seem trivially simple — it's "just a paragraph," after all. But the decisions embedded in a Text component cascade across every page: font size, line height, line length, color, weight, spacing, truncation, and responsive scaling. Getting these defaults right means every paragraph, caption, footnote, and description in your application inherits a cohesive typographic system.
When to use Text:
When NOT to use Text:
Establish your body text size, line height, and measure (line length) as a system-wide foundation. Use the Font Explorer to select a typeface optimized for readability at body sizes, the Clamp Calculator for fluid text scaling, and the Contrast Checker to verify text legibility.
| Size | Font Size | Line Height | Use Case |
|---|---|---|---|
| XS | 12px (0.75rem) | 1.5 (18px) | Fine print, timestamps, legal text |
| SM | 14px (0.875rem) | 1.5 (21px) | Captions, helper text, secondary content |
| MD (Base) | 16px (1rem) | 1.6 (25.6px) | Default body text. Optimized for reading. |
| LG | 18px (1.125rem) | 1.6 (28.8px) | Lead paragraphs, intro text, marketing copy |
| XL | 20px (1.25rem) | 1.5 (30px) | Featured descriptions, article ledes |
For responsive scaling, body text typically stays fixed at 16px — it's already readable on all viewports. If you do scale, use clamp() conservatively: clamp(1rem, 0.5vw + 0.875rem, 1.125rem). Generate values with the Clamp Calculator.
| Variant | Color Token | Use Case |
|---|---|---|
| Default | --color-text-primary | Standard body text |
| Secondary | --color-text-secondary | Supporting content, metadata |
| Tertiary | --color-text-tertiary | Disabled descriptions, placeholders |
| Success | --color-text-success | Confirmation messages |
| Warning | --color-text-warning | Cautionary content |
| Danger | --color-text-danger | Error messages, destructive descriptions |
| Brand | --color-brand | Highlighted or branded text |
| Weight | Value | Use Case |
|---|---|---|
| Regular | 400 | Default body text |
| Medium | 500 | Slightly emphasized text, labels |
| Semibold | 600 | Strong emphasis within body copy |
| Bold | 700 | Critical information, warnings |
font-style: italic — emphasis, citations, foreign termstext-decoration: line-through — deprecated content, price comparisonsfont-family: var(--font-mono) — inline code, technical identifierstext-transform: uppercase; letter-spacing: 0.05em — overlines, category labels| Property | Type | Default | Description |
|---|---|---|---|
size | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Font size variant. |
weight | 'regular' | 'medium' | 'semibold' | 'bold' | 'regular' | Font weight. |
color | string | 'primary' | Text color. Accepts semantic tokens or CSS values. |
variant | 'default' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'danger' | 'default' | Semantic color variant. Overridden by color if both provided. |
align | 'left' | 'center' | 'right' | 'justify' | 'left' | Text alignment. Avoid justify — it creates uneven word spacing. |
as | ElementType | 'p' | Rendered HTML element. Options: p, span, div, label, small, strong, em. |
truncate | boolean | false | Single-line truncation with ellipsis. |
maxLines | number | — | Multi-line truncation via -webkit-line-clamp. |
italic | boolean | false | Applies italic style. |
underline | boolean | false | Applies underline decoration. |
strikethrough | boolean | false | Applies line-through decoration. |
mono | boolean | false | Uses monospace font family. |
uppercase | boolean | false | Transforms text to uppercase with letter-spacing. |
maxWidth | string | — | Sets max-width for controlling line length (measure). Recommended: 65ch. |
className | string | — | Additional CSS classes. |
| Token | Role | Typical Value |
|---|---|---|
--text-font-family | Body typeface | var(--font-family-sans) |
--text-font-family-mono | Monospace variant | var(--font-family-mono) |
--text-color-primary | Default text color | var(--color-text-primary, #111827) |
--text-color-secondary | Secondary text color | var(--color-text-secondary, #6b7280) |
--text-color-tertiary | Tertiary/muted color | var(--color-text-tertiary, #9ca3af) |
--text-size-xs | XS font size | 0.75rem |
--text-size-sm | SM font size | 0.875rem |
--text-size-md | MD/base font size | 1rem |
--text-size-lg | LG font size | 1.125rem |
--text-size-xl | XL font size | 1.25rem |
--text-line-height | Default line height | 1.6 |
--text-line-height-tight | Tight line height | 1.4 |
--text-line-height-loose | Loose line height | 1.8 |
--text-max-width | Optimal reading measure | 65ch |
The 65ch measure (approximately 65 characters per line) is the typographic sweet spot for body readability. Research consistently shows that 45–75 characters per line is optimal; 65ch is the middle of that range. Set this as the default max-width on body text containers.
Select your body typeface with the Font Explorer. Prioritize typefaces with a generous x-height, open counters, and distinct letterforms (especially l/I/1 and O/0 disambiguation) for maximum screen readability.
Text is primarily a static content element, but several contextual states apply:
| State | Visual Treatment | Context |
|---|---|---|
| Default | Rendered at specified size, weight, and color. | Normal content. |
| Truncated (single line) | Text cut with …, full content in title attribute. | Card descriptions, list item subtitles. |
| Clamped (multi-line) | Text cut after N lines with …. Optional "Read more" link. | Feed previews, product descriptions. |
| Selected | Browser-native text selection (::selection styled with brand color). | User selects text for copying. |
| Loading / Skeleton | Text replaced with skeleton lines matching expected content dimensions. | Content loading. See Skeleton. |
| Empty | Placeholder text or em-dash rendered in tertiary color. | Missing data in metadata fields. |
| Highlighted | Background color applied via <mark> element. | Search result highlighting, annotations. |
| Editable | Text becomes an inline editable field on click. | CMS, inline editing interfaces. |
Multi-line truncation uses -webkit-line-clamp, which is supported in all modern browsers (despite the -webkit- prefix):
.text-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
Always provide access to the full text — via a "Read more" toggle, a Tooltip, or an expanded view.
Text accessibility is about ensuring content is perceivable, readable, and adaptable across all user contexts.
Color Contrast (WCAG 1.4.3 Contrast Minimum):
#9ca3af (gray-400) on white backgrounds — that's only 2.9:1, failing AA. Use #6b7280 (gray-500) or darker.WCAG 1.4.12 Text Spacing:
WCAG 1.4.4 Resize Text:
rem, em) — never px for font sizes in production CSS. The clamp() approach with rem-based values satisfies this requirement. Configure via the Clamp Calculator.WCAG 1.4.8 Visual Presentation (Level AAA):
65ch max-width default handles this.text-align: justify) — it creates uneven word spacing that harms readability, especially for dyslexic users.1.6 line height satisfies this.Semantic HTML:
<p> for paragraphs, <span> for inline text, <strong> for important text (not <b>), <em> for emphasis (not <i>), and <small> for fine print.<strong> (importance) from <em> (emphasis) — use them semantically, not for visual styling.title attribute (limited — not accessible to keyboard users) or use aria-label on the container, or a "Read more" disclosure.Language:
lang attribute, use lang on the element: <span lang="fr">bonjour</span>. This ensures screen readers switch pronunciation rules (WCAG 3.1.2 Language of Parts).Do:
max-width of 55–75ch on body text containers (65ch is the sweet spot)variant prop for semantic coloring rather than hardcoded color valuesas — <p> for paragraphs, <span> for inline, <small> for fine printDon't:
text-align: justify — it creates rivers of whitespace that harm readabilityletter-spacing on body text (except uppercase variants) — it disrupts natural reading rhythmReadability Checklist:
<!-- Text Component – Semantic HTML -->
<p class="text text-md">
This is a standard body paragraph with optimal reading settings:
16px font size, 1.6 line height, and a max-width of 65 characters.
</p>
<p class="text text-sm text-secondary">
A secondary caption with smaller text for supporting information.
</p>
<p class="text text-lg text-bold">
A lead paragraph with larger text and bold weight for introductions.
</p>
<small class="text text-xs text-tertiary">
Fine print: Terms and conditions may apply.
</small>
<!-- Truncated text -->
<p class="text text-md text-truncate" title="This very long text will be cut off with an ellipsis">
This very long text will be cut off with an ellipsis
</p>
<!-- Multi-line clamped text -->
<p class="text text-md text-clamp-3">
This paragraph will show a maximum of three lines before being
cut off with an ellipsis. The full content remains in the DOM
for accessibility but is visually hidden beyond the clamp limit.
</p>
<style>
.text {
font-family: var(--text-font-family, system-ui, sans-serif);
color: var(--text-color-primary, #111827);
margin-top: 0;
margin-bottom: 1rem;
max-width: var(--text-max-width, 65ch);
}
.text-xs { font-size: var(--text-size-xs, 0.75rem); line-height: 1.5; }
.text-sm { font-size: var(--text-size-sm, 0.875rem); line-height: 1.5; }
.text-md { font-size: var(--text-size-md, 1rem); line-height: var(--text-line-height, 1.6); }
.text-lg { font-size: var(--text-size-lg, 1.125rem); line-height: 1.6; }
.text-xl { font-size: var(--text-size-xl, 1.25rem); line-height: 1.5; }
.text-secondary { color: var(--text-color-secondary, #6b7280); }
.text-tertiary { color: var(--text-color-tertiary, #9ca3af); }
.text-success { color: var(--color-text-success, #059669); }
.text-warning { color: var(--color-text-warning, #d97706); }
.text-danger { color: var(--color-text-danger, #dc2626); }
.text-bold { font-weight: 700; }
.text-semibold { font-weight: 600; }
.text-medium { font-weight: 500; }
.text-italic { font-style: italic; }
.text-mono { font-family: var(--text-font-family-mono, monospace); }
.text-uppercase { text-transform: uppercase; letter-spacing: 0.05em; }
.text-truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.text-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Selection styling */
.text::selection {
background: var(--color-brand-light, #dbeafe);
color: var(--color-brand-dark, #1e40af);
}
</style>interface TextProps {
size?: "xs" | "sm" | "md" | "lg" | "xl";
weight?: "regular" | "medium" | "semibold" | "bold";
variant?: "default" | "secondary" | "tertiary" | "success" | "warning" | "danger";
color?: string;
align?: "left" | "center" | "right";
as?: React.ElementType;
truncate?: boolean;
maxLines?: number;
italic?: boolean;
mono?: boolean;
uppercase?: boolean;
maxWidth?: string;
className?: string;
children: React.ReactNode;
}
const sizeMap = {
xs: "var(--text-size-xs, 0.75rem)",
sm: "var(--text-size-sm, 0.875rem)",
md: "var(--text-size-md, 1rem)",
lg: "var(--text-size-lg, 1.125rem)",
xl: "var(--text-size-xl, 1.25rem)",
};
const variantColorMap = {
default: "var(--text-color-primary, #111827)",
secondary: "var(--text-color-secondary, #6b7280)",
tertiary: "var(--text-color-tertiary, #9ca3af)",
success: "var(--color-text-success, #059669)",
warning: "var(--color-text-warning, #d97706)",
danger: "var(--color-text-danger, #dc2626)",
};
const weightMap = { regular: 400, medium: 500, semibold: 600, bold: 700 };
function Text({
size = "md",
weight = "regular",
variant = "default",
color,
align = "left",
as: Tag = "p",
truncate = false,
maxLines,
italic = false,
mono = false,
uppercase = false,
maxWidth = "var(--text-max-width, 65ch)",
className,
children,
}: TextProps) {
const style: React.CSSProperties = {
fontFamily: mono ? "var(--text-font-family-mono, monospace)" : "var(--text-font-family, system-ui, sans-serif)",
fontSize: sizeMap[size],
fontWeight: weightMap[weight],
color: color || variantColorMap[variant],
textAlign: align,
fontStyle: italic ? "italic" : undefined,
textTransform: uppercase ? "uppercase" : undefined,
letterSpacing: uppercase ? "0.05em" : undefined,
maxWidth,
marginTop: 0,
marginBottom: Tag === "span" ? undefined : "1rem",
lineHeight: size === "md" || size === "lg" ? "var(--text-line-height, 1.6)" : 1.5,
...(truncate && {
overflow: "hidden" as const,
textOverflow: "ellipsis" as const,
whiteSpace: "nowrap" as const,
}),
...(maxLines && {
display: "-webkit-box" as const,
WebkitLineClamp: maxLines,
WebkitBoxOrient: "vertical" as const,
overflow: "hidden" as const,
}),
};
return (
<Tag className={className} style={style}>
{children}
</Tag>
);
}
// Usage: Standard body paragraph
<Text>This is a standard paragraph with all the readable defaults.</Text>
// Usage: Secondary caption
<Text size="sm" variant="secondary" as="span">
Posted on March 8, 2026
</Text>
// Usage: Lead paragraph
<Text size="lg" weight="medium">
An introductory paragraph with slightly larger text to draw the reader in.
</Text>
// Usage: Truncated card description
<Text size="sm" maxLines={3}>
This product description will be clamped to three lines maximum,
with an ellipsis at the end. Perfect for cards and list items
where space is limited but you want to show a preview.
</Text>
// Usage: Inline code reference
<Text as="span" mono size="sm">
config.theme.colors.primary
</Text>Material Design (MUI) handles body text through <Typography> with variants: body1 (16px), body2 (14px), caption (12px), overline (12px uppercase). The component prop controls the rendered element. MUI's type scale is based on a 14px base with a 1.43 ratio, using Roboto. The gutterBottom prop adds consistent bottom margin. MUI v5 supports the sx prop for inline style overrides using the theme's spacing/color tokens.
Ant Design uses <Typography.Paragraph> for block text and <Typography.Text> for inline text. Both support type (secondary, success, warning, danger), strong, italic, underline, delete (strikethrough), code, mark (highlight), and disabled. The ellipsis prop supports rows (line clamping) with an expandable "Read more" toggle built in — one of the most complete truncation implementations across design systems.
Chakra UI provides a <Text> component inheriting from Box with full style prop support. The fontSize prop accepts the theme's size scale (xs–6xl) or responsive arrays. noOfLines implements line clamping. Chakra pairs well with the @chakra-ui/layout Stack components for controlling paragraph spacing. Color is controlled via color prop mapped to the theme's color tokens.
Bootstrap styles body text via global CSS resets (body { font-size: 1rem; line-height: 1.5; }) and utility classes: .text-muted, .text-primary, .lead (1.25rem intro text), .small (0.875rem). Bootstrap does not provide a Text component — it's CSS-class-based. The $font-size-base, $line-height-base, and $paragraph-margin-bottom Sass variables control global text behavior.
Apple Human Interface Guidelines defines body text through Dynamic Type styles: Body (17pt), Callout (16pt), Footnote (13pt), Caption 1 (12pt), Caption 2 (11pt). All styles respond to the user's Dynamic Type size preference. In SwiftUI, .font(.body) applies the Body style. Apple emphasizes that body text should use San Francisco (system font) for maximum legibility on Apple displays.
Tailwind CSS provides text sizing utilities (text-base, text-sm, text-lg), color utilities (text-gray-600), weight utilities (font-medium), and line-height utilities (leading-relaxed). Truncation: truncate (single line) and line-clamp-3 (multi-line via @tailwindcss/line-clamp plugin, now built in). Tailwind's prose class from @tailwindcss/typography applies opinionated but beautiful body text defaults — optimal line length, spacing, and sizing.