Foundation
Text
BetaThe 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>Links
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.
<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).
| textType | size | purpose |
|---|---|---|
| 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, 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-controlDefault 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.
| textType | Web element | Native 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
Textin 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 / Area | Status |
|---|---|
| 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-labelonly when the visible text is not appropriate for assistive tech (e.g. a glyph or formatted price).
React Native
- Heading variants (
headline-*andheader-*) setaccessibilityRole="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.
accessibilityLabeloverrides the announced name.
Both platforms
- The composite
variantdiscriminated 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-mdper the design spec (links inherit body sizing). - No per-size
--typography-label-font-weight-action-*— label action variants use the sharedcontrol-mdweight token (500).
Text Changelog
2026-05-27 — Review fixes (PR #52)
Changed
- Colour and font-weight tokens aliased under
@theme inlineintext.cssso 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, sofont-{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. twMergeConfigsplit into separatefont-family/font-weight/font-sizegroups so the three classes don’t collapse into each other.data-testidandaria-labeldestructured explicitly instead of via...rest.
2026-05-27 — Initial implementation (PR #52)
Added
Texttypographic primitive for web, mirroring the nativeTextAPI — a single compositevariantdiscriminated 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
classNameprop. Text is a style primitive — the variant is meant to fully determine appearance; openingclassNameinvites 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-*andheader-*). 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, andstyle— none are in Figma. Layout concerns belong in a wrappingView. - Final API:
variant,accessibilityLabel,testID,children. The native equivalents of Figma’saccessibilityLabel+content, plus the RNtestIDconvention. - Renamed the outer stylesheet entry from
varianttofontto avoid the awkwardvariant.variants.varianttriple-nesting inStyleSheet.create.
2026-05-27 — Initial implementation (PR #47)
Added
Texttypographic primitive with a single compositevariantdiscriminated 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).