Text
<Paragraph> renders rich text: one width-constrained block built from <TextSpan> children, each carrying its own style. Text layout has to happen natively — shaping, ligatures, and font fallback belong to the platform (CoreText on iOS, HarfBuzz plus a CJK-aware line breaker on Android) — so the JS side only ships the span structure, and the platform lays it out during the measure pass. The measured height comes back asynchronously through onLayout.
Paragraph basics
width is required — line breaking needs the constraint (x/y default to 0). Everything else is optional:
import { Paragraph, TextSpan } from "@scumble/react";
<Paragraph
x={10}
y={10}
width={330}
fontSize={16}
onLayout={(d) => console.log(`height=${d.height.toFixed(1)} lines=${d.lineCount}`)}
>
<TextSpan text="The quick brown fox jumps over the lazy dog." />
</Paragraph>;Layout-relevant props:
textAlign—"left"(default),"center", or"right"; physical, regardless of text direction (below).lineHeight— line-height multiplier, default1.maxLines— maximum lines;0(default) means unlimited. Overflow is ellipsized when set.onLayout— fires after each re-layout with{ height, lineCount }.
The paragraph also carries default span styling — fontFamily, fontSize, fontWeight, italic, color, letterSpacing — which every span overrides per field.
Spans
<TextSpan> is a data-only declarative child (like the shaders): its props are collected into the paragraph's span list. Text comes from the text prop or JSX children (<TextSpan>hi</TextSpan>; the prop wins when both are given, and children are trimmed):
<Paragraph x={10} y={10} width={330} fontSize={16}>
<TextSpan text="Hello " />
<TextSpan text="skity" color="#3b82f6" fontWeight={700} fontSize={24} />
<TextSpan text=" — " color="#9ca3af" />
<TextSpan italic={true} color="#ef4444">
italic span
</TextSpan>
<TextSpan text=" and back to normal weight text." />
</Paragraph>Spans with different sizes align on a shared baseline. fontWeight is the CSS-style 100–900 scale. Unset fields fall back to the paragraph default, then the platform default: font size 14, weight 400, black.
Shaping features come through as-is — ligatures (Office workflow: fine figure affine), kerning (a bold AVATAR To Ya tightens up), and emoji, including composed ones (flag sequences, ❤️) via the platform fallback fonts.
Text decoration
Spans carry RN-Skia-style text decoration: decoration (underline / overline / line-through — combinable via an array or a numeric bitmask), decorationColor, decorationThickness, and decorationStyle ("solid" | "double" | "dotted" | "dashed" | "wavy"). All four also work as paragraph-level span defaults:
<Paragraph x={10} y={10} width={330} fontSize={16}>
<TextSpan text="plain " />
<TextSpan text="underline " decoration="underline" />
<TextSpan
text="wavy red"
decoration="underline"
decorationColor="#ef4444"
decorationThickness={3}
decorationStyle="wavy"
/>
<TextSpan text=" and strike" decoration={["underline", "line-through"]} />
</Paragraph>Position and thickness come from the span's own font metrics (SkParagraph semantics): on a mixed-size line, each span's lines take its own font's underline/strikeout metrics, so they visibly offset — same as RN-Skia and as CSS with span-level text-decoration. A decoration breaks at line wraps and stops at a maxLines ellipsis (never covering the …), and a gradient or color-filter fill on the paragraph tints the decoration lines too.
Two differences from RN-Skia: decorationThickness is absolute px (0/unset = the font's metric thickness; RN-Skia treats it as a multiplier), and decorationColor unset (0) means "follow the text color" — an explicit fully-transparent black falls back the same way.
Fonts
fontFamily accepts a font name or a font URI. A data: URI embeds a whole ttf/otf inline — decoded synchronously and cached for the process:
const PRESS_START_2P = "data:font/ttf;base64,…";
<Paragraph x={10} y={10} width={330} fontSize={12} fontFamily={PRESS_START_2P}>
<TextSpan>Custom font!</TextSpan>
</Paragraph>;A schemed URI (https://…, file, host schemes) loads asynchronously through the platform font loader: the paragraph first lays out with the default font and re-lays out automatically when the bytes arrive — fonts are a layout input, unlike images. A broken payload falls back to the default font and stays there. One file provides one style (no weight/italic variants from a single URI).
Gradient and filtered text
Shaders and filters place under <Paragraph> like under any shape. A gradient child fills the whole paragraph — span colors then contribute only their alpha — and a <ColorMatrix> recolors the glyphs:
<Paragraph x={10} y={10} width={330} fontSize={22} fontWeight={700}>
<LinearGradient start={[10, 0]} end={[340, 0]} colors={["#8b5cf6", "#ec4899", "#f59e0b"]} />
<TextSpan>Gradient text</TextSpan>
</Paragraph>See Gradients and Filters for the full component set.
Direction and BiDi
direction sets the paragraph's base writing direction for UAX #9 reordering: "ltr" (default), "rtl", or "auto" — which picks the first strong directional character in the text (LTR when there is none):
// Arabic with embedded Latin and digits: the paragraph runs right-to-left,
// the Latin words and numbers stay LTR inside it.
<Paragraph x={10} y={10} width={330} fontSize={16} direction="rtl">
<TextSpan
text="نص ثنائي الاتجاه: العربية واللاتينية 123 والأرقام مختلطة مع English words داخل الجملة."
/>
</Paragraph>
// Hebrew text auto-detected as RTL:
<Paragraph x={10} y={10} width={330} fontSize={16} direction="auto">
<TextSpan text="שלום עולם, זהו טקסט מימין לשמאל עם 456 numbers ו-English." />
</Paragraph>Mixed-direction fragments inside an LTR paragraph reorder in place (an Arabic word renders right-to-left at its position in the line). maxLines truncation puts the ellipsis on the logical trailing side.
textAlign stays physical: left/right always mean the screen edges, no matter the direction — direction="rtl" textAlign="left" gives an RTL paragraph flushed to the left edge.
TIP
Interactive controls (Lynx <view>/<text>) cannot live inside a canvas — place buttons outside the <Canvas>, as the demo app does.
Further reading
- ParagraphDemo.tsx — spans, alignment, custom/remote fonts, truncation,
onLayout, live - BiDiDemo.tsx — RTL, auto detection, mixed runs, direction × alignment