Enhanced SVG Text component with word-wrapping, vertical alignment, rotation, and scale-to-fit capabilities for React visualizations
npx @tessl/cli install tessl/npm-visx--text@3.12.0@visx/text provides an enhanced SVG Text component for React applications that extends the basic SVG text element with advanced text layout capabilities including automatic word-wrapping when width is specified, vertical alignment through the verticalAnchor prop, text rotation via the angle prop, and scale-to-fit functionality.
npm install @visx/textimport { Text, useText, getStringWidth } from "@visx/text";
import type { TextProps, WordsWithWidth, compareFunction } from "@visx/text";For CommonJS:
const { Text, useText, getStringWidth } = require("@visx/text");import React from "react";
import { Text } from "@visx/text";
// Simple text with vertical alignment
const App = () => (
<svg width={400} height={300}>
<Text
x={50}
y={50}
verticalAnchor="start"
fontSize={16}
fill="black"
>
Hello world
</Text>
</svg>
);
// Text with word wrapping
const WrappedTextExample = () => (
<svg width={400} height={300}>
<Text
x={50}
y={50}
width={200}
verticalAnchor="start"
fontSize={14}
fill="blue"
>
This is a long text that will be wrapped automatically when the width constraint is applied.
</Text>
</svg>
);
// Rotated and scaled text
const AdvancedTextExample = () => (
<svg width={400} height={300}>
<Text
x={200}
y={150}
width={180}
angle={45}
scaleToFit="shrink-only"
textAnchor="middle"
verticalAnchor="middle"
fontSize={16}
fill="red"
>
Rotated and fitted text
</Text>
</svg>
);@visx/text is built around three core components:
The architecture follows a separation of concerns where the Text component handles rendering while useText manages all layout computation, and getStringWidth provides the foundational measurement capabilities.
The main React component providing enhanced SVG text rendering with word-wrapping, alignment, rotation, and scaling features.
/**
* Enhanced SVG Text component with advanced layout features
* @param props - TextProps configuration object
* @returns JSX.Element - SVG element containing positioned text
*/
function Text(props: TextProps): JSX.Element;
type TextOwnProps = {
/** CSS class name to apply to the SVGText element */
className?: string;
/** Whether to scale font size to fit the specified width (default: false) */
scaleToFit?: boolean | 'shrink-only';
/** Rotation angle in degrees */
angle?: number;
/** Horizontal text anchor position (default: 'start') */
textAnchor?: 'start' | 'middle' | 'end' | 'inherit';
/** Vertical text anchor position (default: 'end' in useText hook, undefined in Text component) */
verticalAnchor?: 'start' | 'middle' | 'end';
/** Inline styles for the text (used in text measurement calculations) */
style?: React.CSSProperties;
/** Ref for the SVG container element */
innerRef?: React.Ref<SVGSVGElement>;
/** Ref for the text element */
innerTextRef?: React.Ref<SVGTextElement>;
/** X position of the text */
x?: string | number;
/** Y position of the text */
y?: string | number;
/** X offset from the position (default: 0) */
dx?: string | number;
/** Y offset from the position (default: 0) */
dy?: string | number;
/** Line height for multi-line text (default: '1em') */
lineHeight?: React.SVGAttributes<SVGTSpanElement>['dy'];
/** Cap height for vertical alignment calculations (default: '0.71em') */
capHeight?: React.SVGAttributes<SVGTSpanElement>['capHeight'];
/** Font size */
fontSize?: string | number;
/** Font family */
fontFamily?: string;
/** Text fill color */
fill?: string;
/** Maximum width for word wrapping (approximate, words are not split) */
width?: number;
/** Text content to render */
children?: string | number;
};
type TextProps = TextOwnProps & Omit<React.SVGAttributes<SVGTextElement>, keyof TextOwnProps>;
/**
* Note: TextProps extends all standard SVG text element attributes including:
* - Standard SVG styling: stroke, strokeWidth, opacity, etc.
* - Event handlers: onClick, onMouseOver, onMouseOut, etc.
* - Accessibility attributes: aria-*, role, etc.
* Only the custom @visx/text-specific props are documented above.
*/Usage Examples:
import React from "react";
import { Text } from "@visx/text";
// Basic text positioning
<Text x={100} y={50} fontSize={16} fill="black">
Simple text
</Text>
// Word-wrapped text
<Text
x={50}
y={50}
width={200}
fontSize={14}
verticalAnchor="start"
lineHeight="1.2em"
>
This text will wrap to multiple lines when it exceeds 200 pixels width
</Text>
// Rotated text with scaling
<Text
x={150}
y={100}
width={100}
angle={30}
scaleToFit="shrink-only"
textAnchor="middle"
verticalAnchor="middle"
fontSize={16}
>
Rotated scaled text
</Text>
// Styled text with refs and standard SVG attributes
<Text
x={200}
y={150}
className="custom-text"
style={{ fontWeight: 'bold' }}
innerRef={svgRef}
innerTextRef={textRef}
fill="#ff6b6b"
stroke="black"
strokeWidth={0.5}
opacity={0.9}
onClick={(e) => console.log('Text clicked')}
>
Styled text with refs
</Text>React hook that handles text layout calculations including line breaking, vertical positioning, and transform generation.
/**
* Hook for calculating text layout including line breaks and positioning
* @param props - TextProps configuration
* @returns Object containing layout calculation results
*/
function useText(props: TextProps): {
wordsByLines: WordsWithWidth[];
startDy: string;
transform: string;
};
interface WordsWithWidth {
/** Array of words in this line */
words: string[];
/** Calculated pixel width of the line (optional) */
width?: number;
}Usage Example:
import React from "react";
import { useText } from "@visx/text";
import type { TextProps } from "@visx/text";
const CustomTextComponent = (props: TextProps) => {
const { wordsByLines, startDy, transform } = useText(props);
const { x = 0, fontSize, textAnchor = 'start' } = props;
return (
<svg>
<text transform={transform} fontSize={fontSize} textAnchor={textAnchor}>
{wordsByLines.map((line, index) => (
<tspan
key={index}
x={x}
dy={index === 0 ? startDy : props.lineHeight || '1em'}
>
{line.words.join(' ')}
</tspan>
))}
</text>
</svg>
);
};Utility function for measuring the pixel width of text strings using browser SVG measurement.
/**
* Measures the pixel width of a text string using SVG measurement
* Memoized using lodash.memoize with cache key: `${str}_${JSON.stringify(style)}`
* Creates a hidden SVG element with id '__react_svg_text_measurement_id' for measurements
* @param str - Text string to measure
* @param style - Optional CSS style object to apply during measurement
* @returns Pixel width of the text using getComputedTextLength(), or null if measurement fails (non-browser environment, DOM errors)
*/
function getStringWidth(str: string, style?: object): number | null;Usage Example:
import { getStringWidth } from "@visx/text";
// Basic width measurement
const width = getStringWidth("Hello world");
console.log(width); // e.g., 77.5
// Width with custom styling
const styledWidth = getStringWidth("Bold text", {
fontFamily: 'Arial',
fontSize: '16px',
fontWeight: 'bold'
});
console.log(styledWidth); // e.g., 89.2
// Handle potential measurement failures
const safeWidth = getStringWidth("Some text") || 0;/**
* Generic type for comparison functions used in memoization
* @param prev - Previous value (may be undefined)
* @param next - Next value to compare
* @returns Boolean indicating if values are equal
*/
type compareFunction<T> = (prev: T | undefined, next: T) => boolean;documentnull if measurement fails (e.g., in non-browser environments)/(?:(?!\u00A0+)\s+)/ which preserves non-breaking spaces (\u00A0) while splitting on other whitespace\u00A0) for space width measurements to ensure consistent spacingoverflow: 'visible' style