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 useSurfaceInk() from the nearest <Surface> ancestor and resolves the correct on-surface ink colour automatically across all four surface families (neutral, inverse, brand-primary, brand-secondary). Outside any Surface, the tone defaults to 'neutral' — 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. |
| tone | 'default' | 'secondary' | 'error' | 'success' | 'warning' | 'information' | 'default' | Semantic colour, orthogonal to variant. secondary = subtle/muted supporting text; error/success/warning/information = feedback inks. Overrides surface for non-default. No style/colour escape hatch. |
| 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>. |
| maxFontSizeMultiplier | number | 2 | Native — caps OS "Larger Text" font scaling at 2× by default (accessibility / layout safety). Pass 0 for unlimited scaling. |
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-07-08 — maxFontSizeMultiplier prop (TEX-4922)
Added
maxFontSizeMultiplierprop on the nativeText(forwarded to the underlying React Native<Text>). Defaults to2, capping OS “Larger Text” accessibility scaling at 2× to protect layouts while still honouring the user’s preference. Pass0to allow unlimited scaling. This is an accessibility / layout-safety control rather than a style override, so it’s an intentional exception to the otherwise locked-down API.
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).