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
| Type | Matches | Display |
|---|---|---|
link | [label](https://href) | label, blue link (→ href) |
url | https://…, http://…, www.… | as-is, blue link |
email | name@host.tld, mailto:name@host.tld | mailto: stripped, blue link |
phone | tel:+441234567890 | tel: 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 colouring
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 nopattern) overrides that built-in’sonPress/renderText, keeping its pattern and position. - An entry with a
pattern(or a non-built-intype) is added after the defaults. disableDefaultsdrops all built-ins so only yourparserules 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 custompattern, or emphasis given anonPress) reports as a button. A match with no handler is a plain text run. - Setting the block-level
accessibilityLabelprop 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-ruleaccessibilityLabel. maxFontSizeMultiplier(default2) 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 / Area | Status |
|---|---|
| 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.