An open API service indexing awesome lists of open source software.

https://github.com/ShipItAndPray/pretext-forms

Auto-sizing textarea, input, select components using Pretext. No DOM reflow.
https://github.com/ShipItAndPray/pretext-forms

pretext text-layout typescript typography

Last synced: about 1 month ago
JSON representation

Auto-sizing textarea, input, select components using Pretext. No DOM reflow.

Awesome Lists containing this project

README

          

# @shipitandpray/pretext-forms

[![Live Demo](https://img.shields.io/badge/demo-live-brightgreen)](https://shipitandpray.github.io/pretext-forms/) [![npm](https://img.shields.io/npm/v/@shipitandpray/pretext-forms?color=blue)](https://www.npmjs.com/package/@shipitandpray/pretext-forms)

> **[View Live Demo](https://shipitandpray.github.io/pretext-forms/)**
Auto-sizing form fields that know their height before render. Zero CLS. Zero flash.

Built on [@chenglou/pretext](https://github.com/chenglou/pretext) -- a pure JavaScript text measurement and layout engine. No hidden divs, no `scrollHeight` hacks, no post-render reflow.

## Install

```bash
npm install @shipitandpray/pretext-forms @chenglou/pretext
```

## Quick Start

```tsx
import { AutoTextarea } from '@shipitandpray/pretext-forms';

function ChatInput() {
const [text, setText] = useState('');
return (
setText(e.target.value)}
placeholder="Type a message..."
minRows={1}
maxRows={8}
/>
);
}
```

That's it. The textarea calculates its exact height before the browser paints. No flash, no reflow.

## Components

### ``

Drop-in replacement for `` with auto-height.

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `minRows` | `number` | `1` | Minimum visible rows |
| `maxRows` | `number` | `Infinity` | Maximum rows before scrolling |
| `onHeightChange` | `(height: number) => void` | -- | Called when computed height changes |
| `style` | `React.CSSProperties` | -- | Merged with computed height |
| ...rest | `TextareaHTMLAttributes` | -- | All standard textarea props |

```tsx
setText(e.target.value)}
minRows={2}
maxRows={10}
onHeightChange={(h) => console.log('height:', h)}
className="my-textarea"
/>
```

### ``

Auto-width input that grows with its content.

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `minWidth` | `number` | `40` | Minimum width in px |
| `maxWidth` | `number` | `Infinity` | Maximum width in px |
| `style` | `React.CSSProperties` | -- | Merged with computed width |
| ...rest | `InputHTMLAttributes` | -- | All standard input props |

```tsx

```

### ``

Select that auto-sizes to fit the selected option text.

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `style` | `React.CSSProperties` | -- | Merged with computed width |
| ...rest | `SelectHTMLAttributes` | -- | All standard select props |

```tsx
setVal(e.target.value)}>
Small
A Much Longer Option

```

### ``

Label that measures its text content for width transitions.

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `style` | `React.CSSProperties` | -- | Merged with computed width |
| ...rest | `LabelHTMLAttributes` | -- | All standard label props |

## Hook: `useAutoSize`

For custom components that need pre-render text measurement.

```tsx
import { useAutoSize } from '@shipitandpray/pretext-forms';

function CustomField({ text }) {
const { height, lineCount, style } = useAutoSize(
text,
{ font: '400 14px / 20px Inter', fontSize: 14, lineHeight: 20 },
400, // maxWidth
{ minRows: 2, maxRows: 10, padding: { top: 8, right: 12, bottom: 8, left: 12 } },
);
return

{text}
;
}
```

### `useAutoSize` Return Value

```ts
interface AutoSizeResult {
height: number; // Total height including padding
width: number; // Measured width
lineCount: number; // Number of wrapped lines
style: { // Ready-to-spread style object
height: string;
width?: string;
};
}
```

## Utility: `measureTextWidth`

Measures the width of single-line text using pretext layout.

```ts
import { measureTextWidth } from '@shipitandpray/pretext-forms';

const width = measureTextWidth('hello world', '400 14px Inter', 20);
```

## How It Works

1. Reads the element's computed font styles on mount (font-family, font-size, line-height, etc.)
2. On every value change, passes the text + font config to `@chenglou/pretext`'s `prepare()` + `layout()`
3. Gets back exact `lineCount` and `height` -- calculated in pure JS with no DOM measurement
4. Sets the element's `style.height` in the same render, before the browser paints
5. Result: correct dimensions on first paint, zero layout shift

## Comparison

| Feature | pretext-forms | react-textarea-autosize | CSS field-sizing |
|---------|--------------|------------------------|-----------------|
| Zero reflow | Yes | No (1 frame) | Yes |
| Safari support | Yes | Yes | No |
| Firefox support | Yes | Yes | No |
| Input auto-width | Yes | No | Partial |
| Select auto-width | Yes | No | Yes |
| SSR compatible | Yes | No (needs DOM) | Yes |
| Bundle size | ~8KB | ~4KB | 0KB |

## Browser Support

Chrome 90+, Firefox 90+, Safari 14+, Edge 90+.

All browsers that support `Intl.Segmenter` and `OffscreenCanvas` (or fallback to ``).

## Development

```bash
npm install
npm run dev # Watch mode build
npm test # Run tests
npm run demo # Interactive demo
npm run build # Production build (ESM + CJS)
```

## License

MIT