Skip to content

Utility

Parsed Text

Beta

Renders a plain string with parts of it detected, styled and made pressable — the design-system equivalent of react-native-parsed-text, built in-house. Detects URLs, tel: phone numbers, emails (with or without mailto:) and markdown bold/italic/underline out of the box, and lets you override or extend the rules. React Native only for v1.

Native only

ParsedText currently ships for React Native only, so there is no live web preview on this page. Run it in Storybook (native) to try it interactively — see the Storybook link in the header — or read the snippets below.

What it detects out of the box

TypeMatchesDisplay
link[label](https://href)label, blue link (→ href)
urlhttps://…, http://…, www.…as-is, blue link
emailname@host.tld, mailto:name@host.tldmailto: stripped, blue link
phonetel:+441234567890tel: stripped, blue link
bold**text**text, heavier weight
italic*text*text, italic
underline_text_text, underlined

Matching is flat and non-overlapping — rules run in the order above and a matched run is never re-scanned, so **bold** is consumed before italic sees it, and a markdown [label](href) is matched before the bare URL inside it. Detected links navigate on their own (they compose the design system Link) — for a markdown link, the extracted href is used.

Link-like matches (link / url / email / phone) compose the design system Link, so they render as blue links from the @checkatrade/tokens link ramp (colorActionLink…): a blue rest colour (#5263FF on light) and a hover colour on react-native-web. They are underlined in every state (inherited from Link), so they’re distinguishable by more than colour (WCAG). Inside <Surface tone="inverse"> the inverse ramp is used automatically, and dark theme swaps the ramps at the token level. There is no session-visited state (Link doesn’t track it — RN and RN-web can’t read the real :visited state).

Because they compose Link, link segments render as real <a> anchors on react-native-web and navigate on their own — ParsedText derives an href per match (bare www. gets an https:// scheme; email/phone become mailto:/tel:; markdown links use their href) and hands Link the href plus whether it opens in a new tab. That gives correct anchor semantics (right-click, open-in-new-tab, keyboard, screen-reader link nav) on web, and Linking on native — no onPress needed. New-tab links get target="_blank" + rel="noopener noreferrer". Set a rule’s target to change this (only new-tab vs same-tab is honoured); the default is a new tab for url/link. Since navigation is automatic, a rule’s onPress is an optional side-effect (e.g. analytics) and must not navigate again.

Lists

Markdown lists render as block layout. Lines starting with - / * / + (unordered) or 1. / 2) (ordered) become bullet/number rows with a hanging indent, so wrapped lines align under the content. A space after the marker is required (so an inline *italic* isn’t mistaken for a bullet), each item’s content is still inline-tokenised, and when list lines are present the component renders a View (otherwise it stays a single Text). v1 is single-level.

When to use

Use ParsedText when a single string needs parts of it treated specially — links, phone numbers, emails, or lightweight markdown emphasis — without splitting the copy into multiple components by hand. It is the parsed-text utility layered on top of Text.

Overriding and extending

The parse prop merges with the built-ins:

  • An entry with a built-in type (and no pattern) overrides that built-in’s onPress / renderText, keeping its pattern and position.
  • An entry with a pattern (or a non-built-in type) is added after the defaults.
  • disableDefaults drops all built-ins so only your parse rules apply.

There is no per-rule style — a built-in match’s appearance comes from its match type. A custom pattern rule is an in-app action (a button, not a link): it never navigates or takes the link treatment, even if it sets a link type. For a custom look, give a rule a render method that returns your own node (e.g. a basic RN <Text>).

Surface-tone awareness

Because the container is Text, colour flips automatically inside a <Surface tone="inverse"> (and the brand tones). Text segments inherit that colour; link segments switch to the inverse link ramp (see Link colouring).

Accessibility

  • Link segments (link/url/email/phone) report as a link to assistive tech when actionable; any other pressable match (a custom pattern, or emphasis given an onPress) reports as a button. A match with no handler is a plain text run.
  • Setting the block-level accessibilityLabel prop makes the whole block one accessible element and hides the nested segments from assistive tech — omit it when you have interactive segments, and use a per-rule accessibilityLabel.
  • maxFontSizeMultiplier (default 2) caps OS “Larger Text” scaling.

Props

Prop Type Default Description
children * string - The text to parse. Must be a plain string.
variant TextVariant 'body-md' Base Text variant for the whole block.
tone TextTone 'default' Semantic colour, forwarded to Text.
textAlign 'left' | 'center' | 'right' - Horizontal alignment, forwarded to Text.
parse ParseRule[] - Override built-ins by type, or add custom rules.
disableDefaults boolean false Skip all built-in rules; only parse rules apply.
accessibilityLabel string - Override the accessible name of the whole block.
maxFontSizeMultiplier number 2 Caps OS font scaling; 0 = uncapped.
testID / nativeID / accessibilityHint string - Standard a11y/test plumbing (A11yProps), forwarded to Text.

ParseRule

Prop Type Default Description
type 'link' | 'url' | 'email' | 'phone' | 'bold' | 'italic' | 'underline' - Built-in matcher to reuse or override; also drives the segment look.
pattern RegExp - Custom matcher. Takes precedence over type's default pattern.
onPress (matched: string, index: number) => void - Built-in links navigate on their own; here onPress is an optional side-effect (e.g. analytics) — do not navigate in it. For a custom pattern / emphasis match it is the action handler. Receives the raw matched text.
onLongPress (matched: string, index: number) => void - Long-press handler. Not supported on built-in link segments (they compose Link).
renderText (matched: string, groups: string[]) => string - Transform the displayed text. The raw match is still passed to onPress.
accessibilityLabel (display: string, value: string) => string - Accessible name for each matched segment (defaults to the display text). Applied per match.
accessibilityHint string - Accessible hint describing what activating the segment does.
target '_self' | '_blank' | '_parent' | '_top' - Maps to Link's external: _blank opens a new tab (+ rel noopener noreferrer), any other value same-tab. Defaults to new tab for url/link, same-tab for email/phone. Ignored on native.
render (segment) => ReactNode - Fully render the segment yourself (own node + styling). Receives { matched, display, index, onPress, onLongPress }. Wins over type styling.

Import

Native

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

Basic usage

{
  /* Defaults active: url + email + phone + markdown bold/italic/underline */
}
<ParsedText>
  Reach us at https://checkatrade.com, mailto:help@checkatrade.com or tel:+441234567890. **Bold**,
  *italic* and _underlined_ all work.
</ParsedText>;

Side-effects and custom rules

Built-in links (url / email / phone / markdown link) navigate on their own — no onPress needed. Use onPress for an optional side-effect (e.g. analytics), and add a custom pattern for an in-app action (a button, not a link):

<ParsedText
  parse={[
    // Optional analytics on a built-in link — navigation still happens automatically.
    { type: 'url', onPress: (url) => track('link_tap', url) },
    // A custom pattern is an in-app action (button), not a link.
    { pattern: /JOB-\d+/, onPress: (job) => openJob(job) },
  ]}
>
  Job JOB-1024 — email mailto:help@checkatrade.com or visit https://checkatrade.com.
</ParsedText>

Platform status

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

Known token gaps

  • Bold — no Body-Bold font face ships today; bold renders as Medium (500) for now.
  • Italic — no italic font face ships; fontStyle: 'italic' synthesizes an oblique slant, which may render imperfectly with the custom Haffer faces.

No releases yet.