A high-performance, rich-text React component that mathematically pre-measures line breaks to avoid DOM reflows, deeply integrated with Material UI (MUI).
This library provides a drop-in <Text /> wrapper built on top of @chenglou/pretext. While @chenglou/pretext handles the low-level, DOM-free canvas measurements, dynamic-pretext bridges the gap by providing automatic MUI theme synchronization, a robust rich-inline styling engine, and standard typography property support.
- Zero-reflow Line Breaking: Computes exact grapheme line layouts in pure JavaScript before rendering to the DOM.
- MUI Theme Synchronization: Automatically parses MUI variants (e.g.,
variant="h2") and standard typography props (fontSize,lineHeight,color) into the necessary Canvas shorthand formats. - Rich Inline Styling Engine: Apply complex styles—such as gradients, keywords, muted text, or code blocks—via a simple configuration dictionary.
- Shrink-Wrap Containers: Dynamically adjusts container width to exactly fit the computed text width, eliminating layout drift.
- Line Control: Native support for CSS line clamping, explicit
<br>elements, and manual newline (\n) parsing. - Standard Component Surface: Includes standard React features such as
forwardRefand polymorphic HTML rendering (thecomponentprop).
npm install H0N3YC4T/dynamic-pretextPeer Dependencies: react, react-dom, @mui/material, and @chenglou/pretext.
At its core, dynamic-pretext acts as a highly optimized replacement for the standard MUI <Typography> component. It accepts all standard top-level typography properties.
import { Text } from "@/components/Pretext/src";
export const WelcomeBanner = () => {
return (
<Text
variant="body1"
color="text.secondary"
fontSize="1.125rem"
fontWeight={500}
lineHeight={1.6}
component="p"
text="Welcome to the platform. This text is mathematically measured."
/>
);
};By supplying a config object, the wrapper switches to a rich-inline measurement path. This mode parses the text block, identifies special substrings, and renders them with custom React nodes—all while retaining strict pixel-perfect line breaking without relying on the browser to flow the elements.
import { Text } from "@/components/Pretext/src";
export const HighlightedMission = () => {
const missionText = "DynamicPretext provides a fast, zero-reflow layout engine.";
return (
<Text
variant="body1"
text={missionText}
config={{
// Simple string mapping to a built-in rule
"DynamicPretext": "highlight",
// Complex rule targeting with arguments
"zero-reflow layout engine": {
type: "bold",
args: ["primary.main"],
exactCase: false
}
}}
/>
);
};For situations where text should never wrap automatically (e.g., code blocks or structured data), use the manualNewLine prop. The container will force a horizontal expansion unless an explicit \n or <br> is encountered.
<Text
variant="code"
manualNewLine={true}
text={"Line 1\nLine 2<br />Line 3"}
/>The <Text /> component forwards refs and accepts standard HTML attributes alongside the following specialized properties:
| Prop | Type | Default | Description |
|---|---|---|---|
text |
string |
"" |
The raw text payload to be rendered and measured. |
variant |
string |
"body1" |
The MUI typography variant to source default fontFamily, fontSize, and fontWeight from. |
palette |
string |
undefined |
A dot-notation string resolving to a MUI theme color (e.g., "primary.main", "text.secondary"). Alias for color. |
color |
string |
undefined |
A standard CSS color string or MUI theme path. Takes precedence over palette. |
component |
string |
"div" |
The underlying HTML element to render. Passing "span" automatically applies display: inline-flex. |
config |
TextConfig |
{} |
A dictionary mapping substrings or regex patterns to styling rules. |
shrinkWrap |
boolean |
false |
If true, the internal container compresses its width to exactly match the widest measured line. |
manualNewLine |
boolean |
false |
Disables automatic line wrapping. Text will only break on \n or <br>. Aliased as noWrap. |
lineClamp |
number |
undefined |
Implements CSS text-overflow: ellipsis on the specified terminal line limit. |
align |
string |
"left" |
Horizontal text alignment ("left", "center", "right"). Automatically adjusts margin alignment when combined with shrinkWrap. |
wrapBuffer |
number |
1.5% of width |
Subpixel buffer (in px) applied to measurements to prevent fractional pixel rendering overflow. |
lazyWrapping |
boolean |
false |
Disables the internal wrapBuffer. |
The wrapper accepts direct top-level props which will strictly override any inherited variant defaults:
fontSize(e.g.,14,"14px","1.2rem")fontWeight(e.g.,700,"bold")lineHeight(e.g.,1.8,"24px")letterSpacing(e.g.,"0.5px")textTransform(e.g.,"uppercase")
The config object takes the following shape:
type TextConfig = Record<string, string | RuleDef>;
interface RuleDef {
type: string;
args?: string[];
exactCase?: boolean; // Defaults to false
isRegex?: boolean; // Defaults to false
}Built-in Rule Types:
bold: Standard strong weight (args: [colorPath])italic: Emphasized textunderline: Text with a 3px underline offset (args: [colorPath])strike: Line-through decorationhighlight: A glowing accent treatment withtextShadow(args: [colorPath])color: Standard color application (args: [colorPath])muted: Reduced opacity text (args: [opacityFloat])code: Monospace span with a themed background and borderkeyterm: Dotted underline for vocab definitions (args: [colorPath])gradient: Linear gradient text (args: [startColorPath, endColorPath])link: External<a>anchor renderinginternalLink: React Router<Link>rendering (args: [routePath, colorPath])nowrap: ApplieswhiteSpace: "nowrap"to the specific substring
- ResizeObservation: The component strictly binds a
ResizeObserverto its parent container. Initial render paths will mount withvisibility: hiddenif the available width is completely unknown (e.g., zero), waiting for the DOM to settle before painting the measured layout. - The
shrinkWrapLock:shrinkWrapoperates by assigning a precise pixel value to thewidthproperty based on the longest text segment. To prevent cascading overflow in shrinking viewports,maxWidth: 100%is applied simultaneously. Note that if the parent container dynamically grows, ashrinkWrapelement will remain locked at its previously measured pixel width until re-measured. - Subpixel Buffering (
wrapBuffer): Canvas API measurements often differ from DOM text painting by microscopic fractions of a pixel due to browser anti-aliasing engines. By default,dynamic-pretextapplies a minor 1.5% buffer to width calculations to prevent unexpected single-word overflows.