Shape Flow
Text that reflows around a draggable X, powered by Pretext.
Demo
Room to move.
A page is a conversation between what is said and what is left open. Give the words a little room and they find their own rhythm. A line bends, a sentence takes the long way around, and an ordinary paragraph becomes something you can play with. Move the X through this passage. Notice how the words gather on either side, then return to the full width of the page. Nothing has been added to the story. Only the space has changed. In a book, these decisions are fixed in ink. Here, the composition is yours to rearrange. Let the X drift toward an edge, bring it back to the center, or leave it somewhere unexpected. There is more than one way to read a page, and more than one shape a story can take.
Installation
pnpm dlx shadcn@latest add https://ui.arjayby1.xyz/r/shape-flow.jsonCode
import { ShapeFlow } from "@/components/ui/shape-flow";
export function ShapeFlowExample() {
return (
<ShapeFlow
text="A page is a conversation between what is said and what is left open. Give the words a little room and they find their own rhythm. Move the X and watch the paragraph find a new shape."
radius={80}
gap={4}
className="max-w-xl"
/>
);
}API reference
| Prop | Type | Default | Purpose |
|---|---|---|---|
text | string | Required | Plain text to wrap around the X. Whitespace collapses as in a normal paragraph. |
radius | number | 80 | Half the X's width and height in pixels. Shrinks to fit narrow containers. |
gap | number | 4 | Space between the X and the text in pixels. |
height | number | 320 | Minimum height in pixels. The layout and drag area grow together to fit long text. |
fontSize | number | 18 | Text size in pixels. |
lineHeight | number | 28 | Line height in pixels, with a minimum of fontSize. |
fontFamily | string | "Georgia" | Named font family used for both measurement and rendering. |
className | string | None | Outer layout classes, such as max-w-xl. |
Text fills the available spaces on either side and between the arms of the X. Gaps narrower than two characters are skipped. Drag any arm with a mouse, pen, or touch. Focus the X and use arrow keys to move it by 8 pixels, or hold Shift for 32-pixel steps. Home centers the X in the full layout. The X stays inside the drag area when the container resizes.
The full paragraph renders on the server and remains available to screen readers. Once the font loads, the component uses Pretext to measure the text once and reuse those measurements during dragging and resizing. If font measurement is unavailable, it keeps the ordinary paragraph. No automatic animation runs, including with reduced motion enabled.
Use the typography props to keep measurement and rendering in sync. Supply a named font that your app loads, such as Georgia or "Commit Mono", rather than system-ui. The interactive layout requires Canvas 2D, Intl.Segmenter, and ResizeObserver. This component lays out one plain-text paragraph from left to right; rich text and right-to-left paragraph flow are outside its scope.