a1ui

Shape Flow

Text that reflows around a draggable X, powered by Pretext.

Read agent guide

Demo

An exercise in space

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.json

Code

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

PropTypeDefaultPurpose
textstringRequiredPlain text to wrap around the X. Whitespace collapses as in a normal paragraph.
radiusnumber80Half the X's width and height in pixels. Shrinks to fit narrow containers.
gapnumber4Space between the X and the text in pixels.
heightnumber320Minimum height in pixels. The layout and drag area grow together to fit long text.
fontSizenumber18Text size in pixels.
lineHeightnumber28Line height in pixels, with a minimum of fontSize.
fontFamilystring"Georgia"Named font family used for both measurement and rendering.
classNamestringNoneOuter 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.