# @remotion/layout-utils

Dynamic layout utilities — text fitting, measurements, responsive sizing.

**Package:** `@remotion/layout-utils`

## Installation

```bash
npm i @remotion/layout-utils
```

## fitText() — Auto-Size Text

Calculates the font-size needed to fit text within a given width.

```tsx
import { fitText } from '@remotion/layout-utils';

const DynamicText = ({ text }: { text: string }) => {
  const boxWidth = 600;
  const fontFamily = 'Arial';
  const fontWeight = 'bold';
  
  const { fontSize } = fitText({
    text,
    withinWidth: boxWidth,
    fontFamily,
    fontWeight,
  });
  
  // Cap at a max size
  const finalSize = Math.min(80, fontSize);
  
  return (
    <div style={{
      width: boxWidth,
      fontSize: finalSize,
      fontFamily,
      fontWeight,
    }}>
      {text}
    </div>
  );
};
```

### Parameters

| Param | Type | Description |
|-------|------|-------------|
| `text` | `string` | The text to fit |
| `withinWidth` | `number` | Target width in pixels |
| `fontFamily` | `string` | Font family (must be loaded!) |
| `fontWeight?` | `string \| number` | Font weight |
| `letterSpacing?` | `string` | CSS letter-spacing |
| `textTransform?` | `string` | CSS text-transform |
| `fontVariantNumeric?` | `string` | CSS font-variant-numeric |
| `validateFontIsLoaded?` | `boolean` | Throw if font not loaded |

### Returns

```tsx
{ fontSize: number }  // in pixels
```

## Important: Font Must Be Loaded First

```tsx
import { loadFont, waitUntilDone } from '@remotion/google-fonts/Roboto';

// Load font before measuring
const { fontFamily } = loadFont();
await waitUntilDone();

// Now safe to use fitText
const { fontSize } = fitText({
  text: 'Hello',
  withinWidth: 500,
  fontFamily,
});
```

## Common Patterns

### Responsive Headlines

```tsx
const ResponsiveHeadline = ({ text }: { text: string }) => {
  const { width } = useVideoConfig();
  const padding = 100;
  
  const { fontSize } = fitText({
    text,
    withinWidth: width - padding * 2,
    fontFamily: 'Inter',
    fontWeight: 700,
  });
  
  return (
    <h1 style={{
      fontSize: Math.min(fontSize, 120),  // Cap max size
      fontFamily: 'Inter',
      fontWeight: 700,
      padding: `0 ${padding}px`,
    }}>
      {text}
    </h1>
  );
};
```

### Dynamic Quote Cards

```tsx
const QuoteCard = ({ quote, author }: { quote: string; author: string }) => {
  const cardWidth = 800;
  const padding = 40;
  const contentWidth = cardWidth - padding * 2;
  
  const quoteFontSize = Math.min(
    48,
    fitText({
      text: quote,
      withinWidth: contentWidth,
      fontFamily: 'Georgia',
    }).fontSize
  );
  
  return (
    <div style={{ width: cardWidth, padding }}>
      <p style={{ fontSize: quoteFontSize, fontFamily: 'Georgia' }}>
        "{quote}"
      </p>
      <p style={{ fontSize: 24 }}>— {author}</p>
    </div>
  );
};
```

## Tips

### Use `outline` not `border`
Remotion's default stylesheet uses `box-sizing: border-box`, so borders shrink the container. Use `outline` for debugging:

```tsx
// ❌ Border affects measurement
<div style={{ width: 500, border: '2px solid red' }}>

// ✅ Outline doesn't affect layout
<div style={{ width: 500, outline: '2px solid red' }}>
```

### Always Cap Font Size
Without a cap, short text will be enormous:

```tsx
const { fontSize } = fitText({ text: 'Hi', withinWidth: 1000, fontFamily: 'Arial' });
// fontSize might be 500+!

const safeFontSize = Math.min(80, fontSize);
```

### Match All CSS Properties
If you apply `textTransform`, `letterSpacing`, etc. to the element, pass them to `fitText` too:

```tsx
const { fontSize } = fitText({
  text,
  withinWidth: 500,
  fontFamily: 'Arial',
  textTransform: 'uppercase',  // Must match element
  letterSpacing: '0.1em',      // Must match element
});

<div style={{
  fontSize,
  fontFamily: 'Arial',
  textTransform: 'uppercase',
  letterSpacing: '0.1em',
}}>
  {text}
</div>
```

## See Also

- [Remotion docs](https://www.remotion.dev/docs/layout-utils)
- [@remotion/google-fonts](https://www.remotion.dev/docs/google-fonts)
