Skip to content
#ask-design-system

Foundation

Text

Beta

The typographic primitive. Renders a token-driven typographic style with the appropriate semantic element, and reads useSurfaceTone() so it flips colours inside an inverse Surface automatically. Available on web (React) and native (React Native).

Headlines

Degular Display Black 900. Reserved for landing-style display copy.

Find a tradesperson

Find a tradesperson

Find a tradesperson

<Text variant="headline-lg">Find a tradesperson</Text>
<Text variant="headline-md">Find a tradesperson</Text>
<Text variant="headline-sm">Find a tradesperson</Text>

Headers

Haffer Bold 700. Section and subsection headings.

Header XL

Header LG

Header MD

Header SM

Header XS

Header XXS

<Text variant="header-xl">Header XL</Text>
<Text variant="header-lg">Header LG</Text>
<Text variant="header-md">Header MD</Text>
<Text variant="header-sm">Header SM</Text>
<Text variant="header-xs">Header XS</Text>
<Text variant="header-xxs">Header XXS</Text>

Body

Haffer Regular 400. The default for most paragraph copy.

Body XXL

Body XL

Body LG

Body MD (default)

Body SM

Body XS

<Text variant="body-xxl">Body XXL</Text>
<Text variant="body-xl">Body XL</Text>
<Text variant="body-lg">Body LG</Text>
<Text variant="body-md">Body MD (default)</Text>
<Text variant="body-sm">Body SM</Text>
<Text variant="body-xs">Body XS</Text>

Inline links carry an underline and inherit body-md sizing; standalone drops the underline for a button-like row of text.

Read our privacy policy for details.

View all categories
<Text variant="body-md">
Read our <Text variant="link-md-inline">privacy policy</Text> for details.
</Text>
<Text variant="link-md-standalone">View all categories</Text>

Labels

Compact UI bits. Action / button / control purposes use different weights to match the Figma matrix.

Action LGAction MDAction SMButton XLButton MDControl MD
<Text variant="label-lg-action">Action LG</Text>
<Text variant="label-md-action">Action MD</Text>
<Text variant="label-sm-action">Action SM</Text>
<Text variant="label-xl-button">Button XL</Text>
<Text variant="label-md-button">Button MD</Text>
<Text variant="label-md-control">Control MD</Text>

On an inverse surface

Text reads useSurfaceTone() — no inverse prop needed. Wrap the region in Surface tone=inverse and the colour flips automatically.

Heading on a dark surface

Body text inherits the inverse colour.

<Surface tone="inverse" className="rounded-lg p-6 space-y-2">
<Text variant="header-md">Heading on a dark surface</Text>
<Text variant="body-md">Body text inherits the inverse colour.</Text>
</Surface>

When to use

Use Text for every piece of typographic content in the design system. The API is deliberately small — every visual decision is encoded in the variant token. No style overrides, no per-instance colour control, no alignment or truncation props. If you need layout, wrap in a div (web) or View (native).

Why no className prop

Unlike Surface (which is a layout primitive and accepts className for padding, gap, border-radius, etc.), Text is a style primitive — the variant is meant to fully determine its appearance. Opening up className invites one-off colour or weight overrides that drift away from the Figma matrix. If you find yourself wanting to override Text styles, that’s a signal to add the variant upstream rather than patch it at the call site.

Section heading

Body copy below.

Do

Compose with parent layout. Pick a variant from the matrix and let Surface handle the colour context.

Plain body copy without overrides.

Don't

Don't try to recolour Text inline — add a variant in Figma if you genuinely need a new appearance.

Variants

Variant strings are "{textType}-{size}" for headline / header / body, and "{textType}-{size}-{purpose}" for link and label — exact match with the Figma matrix (node 48:2463).

textTypesizepurpose
headlinesm, md, lg
headerxxs, xs, sm, md, lg, xl
bodyxs, sm, md, lg, xl, xxl
linkmdinline, standalone
labelsm, md, lg, xl (button only)action, button, control (md only)

Full list of valid variants:

headline-sm   headline-md   headline-lg
header-xxs    header-xs     header-sm    header-md    header-lg    header-xl
body-xs       body-sm       body-md      body-lg      body-xl      body-xxl
link-md-inline                link-md-standalone
label-sm-action               label-md-action          label-lg-action
label-sm-button               label-md-button          label-lg-button   label-xl-button
label-md-control

Default variant: body-md. The TypeScript union for TextVariant only allows these combinations, so 'headline-xxs', 'body-md-button', or 'label-xl-action' won’t compile.

Surface-tone awareness

Text reads useSurfaceTone() from the nearest <Surface> ancestor and switches to inverse colours automatically. Outside any Surface, the tone defaults to 'default' — safe to use anywhere.

Rendered element

Each variant picks its own semantic element. Heading variants render as <h2> on web and set accessibilityRole="header" on native. Body / link / label variants don’t carry an implicit role.

textTypeWeb elementNative role
headline-*<h2>header
header-*<h2>header
body-*<p>
link-*<span>
label-*<span>

If you need a specific heading level for SEO or landmark reasons, wrap the Text in a <section> / <article> / outer heading — the visible style is decoupled from document-outline level on purpose.

When not to use

  • For actual link navigation. Wrap a styled Text in an <a> (web) or a navigation primitive (native) so the navigation semantics aren’t lost.
  • For button labels. Use Button, which has its own label sizing baked in.

Props

Prop Type Default Description
variant TextVariant 'body-md' Composite variant encoding {textType}-{size} (or {textType}-{size}-{purpose} for link and label). Discriminated union — only Figma-matrix combinations compile.
aria-label string - Web — overrides the accessible name when the visible text is not appropriate for assistive tech.
accessibilityLabel string - Native — overrides the accessible name.
children ReactNode - Text content. Inline Text nested inside a body Text picks up its own variant.
data-testid string - Web — forwarded to the rendered element.
testID string - Native — forwarded to the underlying RN <Text>.

Import

Web

import { Text } from '@checkatrade/components-web';

Native

import { Text } from '@checkatrade/components-native';

Basic usage

// Common heading + body
<Text variant="header-lg">Section heading</Text>
<Text variant="body-md">Some descriptive copy.</Text>

// Inline link styled inside body copy
<Text variant="body-md">
  Read our <Text variant="link-md-inline">privacy policy</Text> for details.
</Text>

// Label variants for compact UI bits
<Text variant="label-md-action">Continue</Text>
<Text variant="label-md-control">Select all</Text>

Linking the styled Text

<a href="/privacy" className="no-underline">
  <Text variant="link-md-inline">Privacy policy</Text>
</a>

Inside an inverse Surface

<Surface tone="inverse" className="rounded-lg p-6">
  <Text variant="header-md">Heading on a dark surface</Text>
  <Text variant="body-md">Body text inherits the inverse colour.</Text>
</Surface>

Platform status

Platform / AreaStatus
Design (Figma) Beta
Web (React) Beta
Native (React Native) Beta
iOS (Swift) Planned
Android (Kotlin) Planned
Accessibility audit Planned

Accessibility

Web

  • Heading variants render as <h2> — assistive tech announces them as headers automatically.
  • Body variants render as <p>. Link / label variants render as <span> and don’t carry an implicit role.
  • Pass aria-label only when the visible text is not appropriate for assistive tech (e.g. a glyph or formatted price).

React Native

  • Heading variants (headline-* and header-*) set accessibilityRole="header" on the underlying RN <Text> so VoiceOver and TalkBack announce them as headers.
  • Body / link / label variants leave the role unset — they fall back to RN’s default text role.
  • accessibilityLabel overrides the announced name.

Both platforms

  • The composite variant discriminated union prevents invalid combinations (headline-xxs, body-md-button, label-xl-action) at compile time.

Known token gaps

  • No --typography-link-font-size-* tokens — link variants reuse --typography-body-font-size-md / --typography-body-line-height-md per the design spec (links inherit body sizing).
  • No per-size --typography-label-font-weight-action-* — label action variants use the shared control-md weight token (500).

Text Changelog

2026-05-27 — Review fixes (PR #52)

Changed

  • Colour and font-weight tokens aliased under @theme inline in text.css so Tailwind v4 actually generates the utilities. The tokens package declares its variables under :root, which does not extend the theme — the previous classes rendered no style.
  • Renamed colour utilities to text-text-{heading,body}(-inverse)? to match the new aliases.
  • Font-weight bound via paired --font-* / --font-weight-* aliases on the same suffix, so font-{headline,header,body,…} carries both family and weight from tokens. Distinct weight utilities (font-link-inline, font-link-standalone, font-action, font-control, font-button-{sm,md,lg,xl}) cover variants whose weight differs from the family default.
  • twMergeConfig split into separate font-family / font-weight / font-size groups so the three classes don’t collapse into each other.
  • data-testid and aria-label destructured explicitly instead of via ...rest.

2026-05-27 — Initial implementation (PR #52)

Added

  • Text typographic primitive for web, mirroring the native Text API — a single composite variant discriminated union encoding the Figma matrix (node 48:2463): headline-{sm,md,lg}, header-{xxs,xs,sm,md,lg,xl}, body-{xs,sm,md,lg,xl,xxl}, link-md-{inline,standalone}, label-{sm,md,lg}-{action,button}, label-xl-button, label-md-control. Invalid combinations are unrepresentable at the type level.
  • Surface-tone awareness: reads useSurfaceTone() and flips to inverse colours inside <Surface tone="inverse">.
  • Semantic element per variant: headline-* / header-*<h2>, body-*<p>, link-* / label-*<span>. Heading levels stay decoupled from visible style; wrap in <section> / <article> when a specific document-outline level is needed.
  • Token-backed Tailwind utilities under --text-{headline,header,body,link,label-*}-{size} (paired with --line-height), --font-{headline,header,body,label-action,label-button}, and --color-text-{heading,body}(-inverse)?.
  • Storybook stories: Default, Headline, Header, InlineLink, VariantMatrix, OnInverseSurface.

Notes

  • No className prop. Text is a style primitive — the variant is meant to fully determine appearance; opening className invites one-off colour or weight overrides that drift from the Figma matrix.

Known token gaps

  • No --typography-link-font-size-* tokens — link variants reuse --typography-body-font-size-md / --typography-body-line-height-md (links inherit body sizing per the spec).
  • No per-size --typography-label-font-weight-action-* — label action variants use the shared control-md weight token (500).

Text Changelog

2026-05-27 — Heading a11y role (PR #47)

Added

  • accessibilityRole="header" set on the underlying React Native <Text> for any variant whose role resolves to 'heading' (headline-* and header-*). VoiceOver / TalkBack now announce these as headers. Body / link / label variants leave the role unset and fall back to RN’s default.

Fixed

  • Page titles using a heading variant were previously announced as plain text by screen readers.

2026-05-27 — API locked to the Figma matrix (PR #47)

Changed

  • API stripped back to the props that exist in the Figma source of truth. Removed emphasis, align, numberOfLines, and style — none are in Figma. Layout concerns belong in a wrapping View.
  • Final API: variant, accessibilityLabel, testID, children. The native equivalents of Figma’s accessibilityLabel + content, plus the RN testID convention.
  • Renamed the outer stylesheet entry from variant to font to avoid the awkward variant.variants.variant triple-nesting in StyleSheet.create.

2026-05-27 — Initial implementation (PR #47)

Added

  • Text typographic primitive with a single composite variant discriminated union mirroring the Figma matrix (node 48:2463): headline-{sm,md,lg}, header-{xxs,xs,sm,md,lg,xl}, body-{xs,sm,md,lg,xl,xxl}, link-md-{inline,standalone}, label-{sm,md,lg}-{action,button}, label-xl-button, label-md-control. Invalid combinations are unrepresentable at the type level.
  • Surface-tone awareness: reads useSurfaceTone() from the nearest <Surface> ancestor and switches to inverse colours automatically. Outside any Surface the tone defaults to 'default'.
  • Storybook stories: Default, Headlines, Headers, Body, Links, Labels, SurfaceToneComparison.
  • Tests: rendering, the full variant matrix as a smoke test, surface-tone propagation, fallback outside a Surface, all 9 heading-role variants + 5 non-heading variants for accessibilityRole (56 tests total).