Skip to content
v2.5.0
English
Русский
Documentation

Text

Text is the base body-typography primitive.
The quick brown fox jumps over the lazy dog.
text.tsx · 3 lines
import { Text } from '@vega-ui/react';

<Text size={3}>The quick brown fox jumps over the lazy dog.</Text>
Browser support
Widely available
  • Chrome 111
  • Edge 111
  • Firefox 113
  • Safari 15.4
  • Safari on iOS 15.4
Bundle size
Tested in
25 tests

Summary

Text is the base body-typography primitive. It controls size and weight while allowing semantic composition through asChild.

Imports

import { Text, type TextProps, type TextSize } from '@vega-ui/react';

Exported Types

  • TextProps
  • TextSize = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11

Basic Usage

basic-usage.tsx · 11 lines
import { Text } from '@vega-ui/react';

export default function BasicUsage() {
  return (
    <Text asChild size={3}>
      <p style={{ maxWidth: '60ch' }}>
        Your trial ends in 5 days. Add a payment method to keep your projects, history and integrations.
      </p>
    </Text>
  );
}

Variants

  • Size: 1 through 11
  • Weight: 400, 500, 600, 700, 900
  • Composition: default element or asChild

Edge Cases

  • asChild changes the rendered element but keeps the text styling contract.
  • white-space: pre-line preserves newline breaks from content strings.
  • Very large text sizes should still be checked against layout and line length.

Common Mistakes

  • Using Text where document headings should be semantic Heading.
  • Overriding weight and size ad hoc instead of using the design scale.
  • Forgetting that multiline content will preserve line breaks.

Examples

Basic

Basic: article body

Text renders a <span> by default; paragraphs use asChild with a <p> so the copy keeps paragraph semantics. A nested Text with fontWeight emphasizes a fragment, and the byline is muted through --t-color.

body-copy.tsx · 24 lines
import { Heading, Text } from '@vega-ui/react';
import type { CSSProperties } from 'react';

export default function BodyCopy() {
  return (
    <article style={{ display: 'grid', gap: 'var(--spacing-6)', width: '100%', maxWidth: '60ch' }}>
      <header style={{ display: 'grid', gap: 'var(--spacing-2)', marginBlockEnd: 'var(--spacing-2)' }}>
        <Heading as='h3' size={6}>Scheduled maintenance on March 18</Heading>
        <Text size={2} style={{ '--t-color': 'var(--label-secondary)' } as CSSProperties}>
          Posted by the Platform team
        </Text>
      </header>

Controlled/Stateful

Controlled/Stateful: copy derived from state

The sentence is computed from the seat count with Intl.PluralRules and switches to a different message at zero. aria-live='polite' announces the change after each click without moving focus.

dynamic-helper-copy.tsx · 33 lines
import { Button, Text } from '@vega-ui/react';
import { useState } from 'react';

const totalSeats = 5;
const plural = new Intl.PluralRules('en-US');

export default function DynamicHelperCopy() {
  const [assigned, setAssigned] = useState(3);
  const remaining = totalSeats - assigned;
  const seatWord = plural.select(remaining) === 'one' ? 'seat' : 'seats';

  return (

Form/Integration

Form/Integration: hint and character counter

Both the hint and the live counter are Text elements referenced from the input with aria-describedby, so screen readers read them with the field. The value is submitted with FormData, and reset clears the counter together with the input.

helper-text-below-a-field.tsx · 58 lines
import { Button, Label, Text, TextField, TextFieldInput } from '@vega-ui/react';
import { useState, type CSSProperties, type FormEvent } from 'react';

const maxLength = 40;

export default function HelperTextBelowAField() {
  const [headline, setHeadline] = useState('');
  const [saved, setSaved] = useState('');

  const onSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const data = new FormData(event.currentTarget);

Layout/Overlay

Layout/Overlay: description in a dialog

The dialog takes its accessible name from the heading and its description from the Text paragraph through aria-labelledby and aria-describedby, so screen readers announce the consequences as soon as the dialog opens.

descriptive-copy-in-a-dialog.tsx · 58 lines
import {
  Button,
  Dialog,
  DialogClose,
  DialogContent,
  DialogTrigger,
  Heading,
  Icon,
  IconButton,
  Text,
} from '@vega-ui/react';
import { XIcon } from '@vega-ui/icons';

Error

Error: inline validation message

The coupon is checked on submit. An unknown code shows a Text message in the error color, links it with aria-describedby and sets aria-invalid; editing the field clears the error. Color is not the only signal: the message itself says what is wrong.

inline-validation-copy.tsx · 49 lines
import { Button, Label, Text, TextField, TextFieldInput } from '@vega-ui/react';
import { useState, type CSSProperties, type FormEvent } from 'react';

const validCodes = ['SPRING20', 'WELCOME10'];

export default function InlineValidationCopy() {
  const [error, setError] = useState('');
  const [applied, setApplied] = useState('');

  const onSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const data = new FormData(event.currentTarget);

Disabled

Disabled: explanation for a disabled section

The whole Fieldset is disabled and a muted Text explains why. The explanation is linked to the fieldset with aria-describedby, because disabled controls cannot receive focus and a nearby note is easy to miss.

muted-explanatory-text.tsx · 26 lines
import { Button, Fieldset, FieldsetLegend, Label, Text, TextField, TextFieldInput } from '@vega-ui/react';
import type { CSSProperties } from 'react';

export default function MutedExplanatoryText() {
  return (
    <div style={{ display: 'grid', gap: 'var(--spacing-8)', width: '100%' }}>
      <Fieldset disabled aria-describedby='export-locked-note' style={{ display: 'grid', gap: 'var(--spacing-12)' }}>
        <FieldsetLegend style={{ marginBlockEnd: 'var(--spacing-8)' }}>Scheduled exports</FieldsetLegend>
        <div style={{ display: 'grid', gap: 'var(--spacing-3)' }}>
          <Label htmlFor='export-email'>Send CSV to</Label>
          <TextField>
            <TextFieldInput id='export-email' name='exportEmail' type='email' defaultValue='finance@acme.io' />

Edge

Edge: newlines in data

Text uses white-space: pre-line: newlines in a string become line breaks, which suits an address. Hard-wrapped copy from a CMS renders the same way, so override it with whiteSpace: 'normal' when the newlines are not meaningful.

multiline-content.tsx · 13 lines
import { Text } from '@vega-ui/react';

const address = 'Acme GmbH\nTorstraße 140\n10119 Berlin\nGermany';
const description = 'Soft merino wool sweater with a relaxed fit,\nribbed cuffs and a crew neck.\nMachine washable at 30\u00a0°C.';

export default function MultilineContent() {
  return (
    <div style={{ display: 'grid', gap: 'var(--spacing-8)', width: '100%' }}>
      <Text size={3}>{address}</Text>
      <Text size={3} style={{ whiteSpace: 'normal' }}>{description}</Text>
    </div>
  );

Edge: long text in a narrow column

Truncation needs a block box: Text is an inline <span>, so textOverflow: 'ellipsis' works only together with display: 'block', overflow: 'hidden' and whiteSpace: 'nowrap'; title keeps the full name available. Long unbroken strings such as emails wrap with overflowWrap: 'anywhere'.

long-text-overflow.tsx · 27 lines
import { Text } from '@vega-ui/react';

const files = [
  'Q1-2026-board-presentation-final-v7-approved-by-legal.pdf',
  'customer-interviews-transcripts-berlin-and-lisbon.docx',
  'logo.svg',
];

export default function LongTextOverflow() {
  return (
    <div style={{ display: 'grid', gap: 'var(--spacing-4)', width: '100%' }}>
      {files.map((file) => (

Edge: right-to-left names in a sentence

User names in Hebrew or Arabic inside an English sentence can reorder the neighbouring numbers and punctuation. Wrapping the name in <bdi> through asChild isolates its direction.

bidi-user-content.tsx · 21 lines
import { Text } from '@vega-ui/react';

const comments = [
  { author: 'Maya Cohen', handle: 'מאיה כהן', count: 3 },
  { author: 'Omar Haddad', handle: 'عمر حداد', count: 12 },
];

export default function BidiUserContent() {
  return (
    <div style={{ display: 'grid', gap: 'var(--spacing-4)' }}>
      {comments.map((comment) => (
        <Text key={comment.author} size={3}>

Anatomy

Overview

Text is a single-part typography primitive. The root owns the full public contract for body copy sizing, weight, and semantic composition.

Required Parts

Text

Required. Renders the text node with VegaUI body typography styles.

Optional Parts

There are no exported child parts.

Composition Order

  1. Text
  2. content or asChild target

Valid Composition Patterns

The outer Text renders a <p> through asChild; inline Text elements inside it stay <span>s or render semantic elements such as <time>.

valid-composition-patterns.tsx · 15 lines
import { Text } from '@vega-ui/react';

export default function ValidCompositionPatterns() {
  return (
    <Text asChild size={3}>
      <p>
        Order <Text fontWeight={700}>#4821</Text> ships on{' '}
        <Text asChild>
          <time dateTime='2026-03-14'>March 14</time>
        </Text>
        .
      </p>

Invalid Composition Patterns

Text used as a heading replacement

This may look correct visually but loses heading semantics.

Arbitrary raw CSS used instead of the size scale

This creates drift from the shared typography system.