BetaData display

Typography

Text, by the role it plays. Ten roles fix size, line height, weight and family together, so a heading cannot be set in a body weight and a caption cannot be set in a display size.

Overview

Text, by the role it plays. Ten roles fix size, line height, weight and family together, so a heading cannot be set in a body weight and a caption cannot be set in a display size.

First example

<Typography>Trois projets en cours, un archivé.</Typography>

Anatomy

Two components, and neither is a slot of the other.

<Typography variant="h4">
  Supprimer <TextSpan fontWeight="700">trois projets</TextSpan>
</Typography>
  • Typography — one text node, carrying a role.
  • TextSpan — a bare React Native Text, for a fragment styled apart from the text around it.

Typography publishes no slot and no context. A span is not a slot of a paragraph: React Native already makes a nested Text inherit its parent's font, size, weight and colour, so TextSpan needs nothing from Typography to work. Reimplementing that inheritance is exactly what the legacy TextSpanContext did, and it is gone.

Usage

Basic

<Typography>Trois projets en cours, un archivé.</Typography>

variant defaults to body.

The roles

<Typography variant="h1">Projets</Typography>
<Typography variant="h2">Projets</Typography>
<Typography variant="h3">Projets</Typography>
<Typography variant="h4">Projets</Typography>
<Typography variant="h5">Projets</Typography>
<Typography variant="h6">Projets</Typography>
<Typography variant="body">Trois en cours, un archivé.</Typography>
<Typography variant="body-sm">Trois en cours, un archivé.</Typography>
<Typography variant="body-xs">Trois en cours, un archivé.</Typography>
<Typography variant="code">npm i @xaui/native</Typography>

code is the one role that is a shape as well as a scale: it sits on the default fill, rounded, padded, and alignSelf: 'flex-start' so it hugs the word. A Text carrying a background stretches to its container otherwise, and the fill would paint a band across the line instead of a chip. The fill is a token and not a role, so color on a code tints its ink and leaves the chip neutral — see Colour.

h1h6 name a step on the scale, not an HTML tag — React Native has no document outline. Announcing a heading to a screen reader stays explicit, see Accessibility.

There is no size prop and no weight prop. A role is chosen as a whole, which is what makes weight="light" on a heading, or size="lg" on a caption, unwritable rather than merely discouraged.

A fragment of a line

<Typography variant="h4">
  Supprimer <TextSpan fontWeight="700">trois projets</TextSpan> définitivement
</Typography>

A TextSpan overrides only what it names and inherits the rest. It nests as deep as you like:

<Typography>
  italic,{' '}
  <TextSpan fontStyle="italic">
    then <TextSpan fontWeight="700">bold as well</TextSpan>
  </TextSpan>
</Typography>

To change role mid-sentence, nest a Typography rather than a TextSpan — a span that named a role would be a Typography.

Colour

<Typography color="#7c3aed">a tinted paragraph</Typography>

color is a raw value, never a token. In a text component there is only one thing to tint, so nothing has to say where it lands. A theme token is passed as the raw value it is:

<Typography color={theme.colors.danger}>Suppression définitive</Typography>

Alignment and truncation

Neither has a prop, and neither needs one.

<Typography textAlign="center">Centré</Typography>
<Typography numberOfLines={1}>Une seule ligne, puis une ellipse…</Typography>

textAlign is a TextStyle key, so the style props below already expose it. numberOfLines is React Native's own prop, forwarded like every other. A prop of ours would be a second name for the same thing, and a layer to keep honest.

Style as props

<Typography fontSize={17} letterSpacing={1}>off the scale, and it says so</Typography>
<Typography variant="h5" marginBottom={8}>a heading with room under it</Typography>

Full React Native names, full React Native values: fontSize={17} is 17 points, never a step on a scale. They resolve after the role and before style.

As another element

<Typography variant="h5" asChild>
  <Link href="/projects">Voir les projets</Link>
</Typography>

The child element receives the ref, the role's style and the props.

It has to render text. A View-based child — a Pressable, say — takes a fontSize it cannot use, and breaks the Text inheritance a nested TextSpan depends on. For a press on text, Text carries its own onPress:

<Typography variant="h5" asChild>
  <Text onPress={open}>a Text with its own onPress</Text>
</Typography>

Everything else goes through style

A gradient, a shadow, a text decoration colour: style. It is applied last and wins over everything above.

Root props

TypographyProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
childrenReactNodeNo
variantTypographyVariant | undefinedNoThe role. It fixes size, line height, weight and family at once.
colorstring | undefinedNoA raw tint (`'#7c3aed'`), never a token (R7). In a text component there is only one thing to tint, so it lands on the text itself.
asChildboolean | undefinedNoR12 — the child element becomes the text node, keeping this variant's style.
styleStyleProp<TextStyle>No

Slots

TextSpanProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
childrenReactNodeNo
asChildboolean | undefinedNoR12 — the child element becomes the span.
styleStyleProp<TextStyle>No

Variants, sizes and colour

This component adds no visual axis of its own. The values it does take are in the generated types above and in the live demo.

Accessibility

A Text announces as text, so the component sets no accessibilityRole default — setting one would override whatever a caller's element brought through asChild.

Because h1h6 name a step on the scale rather than a document outline, a heading a screen reader should announce as one says so:

<Typography variant="h3" accessibilityRole="header">
  Projets
</Typography>

Migration from legacy

Legacyv1
<Typography variant="displayLarge"><Typography variant="h1">
<Typography variant="headlineMedium"><Typography variant="h3">
<Typography variant="bodyMedium"><Typography>
<Typography variant="bodySmall"><Typography variant="body-sm">
size + weight propsthe role alone — the combination is chosen, not composed
<TextSpan color fontWeight …>the same keys, now every TextStyle key (R14)
<TextSpan align="left">textAlignleft and right are gone (R13)
TextSpanContextremoved — React Native's own Text inheritance does it

Implementation notes

Availability

The same component ships in both renderers, and this page documents both — there is one API, so there is one page.

import { TextSpan, Typography } from '@xaui/native/typography' // React Native
import { TextSpan, Typography } from '@xaui/hybrid/typography' // web

Every variant, prop, default and precedence rule below is identical in the two packages, and the numbers keep their meaning: fontSize={17} is 17 points on Native and 17 CSS pixels on the web, because Hybrid converts each length against the document root rather than against the inherited text size.

Only what the platform makes impossible to share differs:

  • ref targets a React Native Text on Native and an HTMLElement on the web.
  • Press and layout handlers receive their platform's event — a React DOM event under Hybrid, where onPress is also reachable from the keyboard on Enter and Space.
  • Typography renders a <span> under Hybrid. accessibilityRole maps to the matching ARIA role, so accessibilityRole="header" announces as a heading in both.
  • Props naming an iOS or Android mechanism the web has no equivalent for — dynamicTypeRamp, adjustsFontSizeToFit, lineBreakStrategyIOS — stay accepted so the same source compiles against either package, and are dropped before the DOM rather than forwarded as unknown attributes. The ones the web can honour are translated: numberOfLines becomes a line clamp, selectable a user-select.

Hybrid is on the beta dist-tag while the port proceeds.

The role table

variantsizeline heightweightfamily
h14xl · 3640boldheading
h23xl · 3036boldheading
h32xl · 2432boldheading
h4xl · 2028semiboldheading
h5lg · 1828semiboldheading
h6md · 1624semiboldheading
bodymd · 1624regularbody
body-smsm · 1420regularbody
body-xsxs · 1216regularbody
codesm · 1420regularmono

h1h3 are tracked slightly tighter than the rest: the letter spacing that keeps 14pt legible reads as loose and unset at 36.

code is the one role that names a fill as well as ink — inline code reads as code by sitting on a surface rather than by its font alone.