npm package: @feliperohdee/satori
Description: Enlightened library to convert HTML and CSS to SVG.
Latest version: 0.0.117

**Satori**: Enlightened library to convert HTML and CSS to SVG.

[![npm version](https://img.shields.io/npm/v/@feliperohdee/satori.svg)](https://www.npmjs.com/package/@feliperohdee/satori)

## Install

Package: [`@feliperohdee/satori`](https://npmjs.com/@feliperohdee/satori)

```bash
npm install @feliperohdee/satori
```

## Improvements Over Original

This fork extends the [original Satori](https://github.com/vercel/satori) with:

- [**Fonts API**](#fonts-api) — auto-detect fonts from JSX and load them on demand via a config object
- [**Preset Selector**](#preset-selector) — choose between Chromium defaults or Tailwind preflight reset
- [**React Hooks Support**](#react-hooks-support) — render components that use hooks (e.g., lucide-react icons) without crashing
- [**Tailwind CSS**](#tailwind-css) — convert `className` props into inline styles using the Tailwind CSS v4 compiler, with callback API to extend the default theme
- [**Faux Bold**](#faux-bold) — synthetic bold when only a regular weight font is loaded
- [**CSS Enhancements**](#css-enhancements) — `backdrop-filter`, `mix-blend-mode`, `background-position` with keyword sizes
- [**Transform Element**](#transform-element) — intercept and modify the React element tree before rendering

## Fonts API

The `fonts` option accepts either a static array of font objects or a config object that auto-detects fonts from the JSX tree and loads them on demand.

### Static Fonts

Pass `fonts` as an array of `Font` objects. Each font requires `name`, `data` (Buffer/ArrayBuffer), `weight`, and optionally `style`. Multiple fonts can be passed and used via `fontFamily`.

Supported formats: TTF, OTF, and WOFF. WOFF2 is not supported.

```js
await satori(
  <div style={{ fontFamily: 'Inter' }}>Hello</div>,
  {
    width: 600,
    height: 400,
    fonts: [
      {
        name: 'Inter',
        data: inter,
        weight: 400,
        style: 'normal',
      },
      {
        name: 'Inter',
        data: interBold,
        weight: 700,
        style: 'normal',
      },
    ],
  }
)
```

### Dynamic Fonts

Pass `fonts` as a `FontsConfig` object to auto-detect fonts from the JSX element tree and load them on demand:

```js
await satori(
  <div style={{ fontFamily: 'Inter' }}>Hello</div>,
  {
    width: 600,
    height: 400,
    fonts: {
      // Font to load when no fontFamily is specified in JSX
      defaultFont: { family: 'Inter', key: 'inter', weight: 400 },
      // Callback to load fonts by key and weight
      load: async (font) => {
        const url = `https://cdn.jsdelivr.net/fontsource/fonts/${font.key}@latest/latin-${font.weight}-normal.ttf`;
        const res = await fetch(url);
        if (!res.ok) { return null; }
        return { data: await res.arrayBuffer(), name: font.key, weight: font.weight };
      },
      // Optional: pre-loaded font data to include
      data: [{ name: 'CustomFont', data: customFontBuffer, weight: 400 }],
      // Optional: map font family names to different keys
      aliases: { 'ui-sans-serif': 'inter' },
      // Optional: pass fontWeight from CSS to the load callback
      resolveFontWeight: true,
    },
  }
)
```

| Property | Required | Description |
|---|---|---|
| `defaultFont` | Yes | `DetectedFont` to use when no `fontFamily` is specified in JSX |
| `load` | Yes | Async callback receiving a `DetectedFont`, returns `Font`, `Font[]`, image URL (`string`), or `null` |
| `data` | No | Pre-loaded `Font[]` merged with dynamically detected fonts |
| `aliases` | No | Map font family names to different keys. Value can be a `string` or `{ key: string; url: string }` |
| `resolveFontWeight` | No | When `true`, passes the CSS `fontWeight` value to the `load` callback instead of defaulting to `400` |

### Dynamic Loading (Emojis and Missing Glyphs)

When `fonts` is a config object, the `load` callback is also called during layout for missing glyphs (e.g., CJK characters, emojis). In this case, the `DetectedFont` will include `languageCode` and `segment` fields to identify what needs to be loaded. The callback can return a `string` (image URL for grapheme rendering) or a `Font` object.

```jsx
await satori(
  <div>👋 你好</div>,
  {
    fonts: {
      defaultFont: { family: 'Inter', key: 'inter', weight: 400 },
      load: async (font) => {
        // When loading missing glyphs, `font.languageCode` is set.
        // `font.segment` is the content to render.
        if (font.languageCode === 'emoji') {
          return `data:image/svg+xml;base64,...`
        }

        if (font.languageCode) {
          return loadFontForLanguage(font.languageCode)
        }

        // Initial font detection from JSX
        const url = `https://cdn.jsdelivr.net/fontsource/fonts/${font.key}@latest/latin-${font.weight}-normal.ttf`;
        const res = await fetch(url);
        if (!res.ok) { return null; }
        return { data: await res.arrayBuffer(), name: font.key, weight: font.weight };
      }
    }
  }
)
```

### Types

```ts
type Font = {
  data: Buffer | ArrayBuffer;
  lang?: string;
  name: string;
  style?: 'normal' | 'italic';
  weight?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
};

type DetectedFont = {
  family: string;
  key: string;
  languageCode?: string;  // Set when loading missing glyphs (e.g., 'emoji', 'ja-JP', 'zh-CN')
  segment?: string;       // The content that needs a font/image
  url?: string;
  weight: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
};

type FontsConfig = {
  aliases?: Record<string, string | { key: string; url: string }>;
  data?: Font[];
  defaultFont: DetectedFont;
  load: (font: DetectedFont) => Promise<Font | Font[] | string | null>;
  resolveFontWeight?: boolean;
};
```

## React Hooks Support

The original Satori crashes with "Invalid hook call" when rendering components that use React hooks (e.g., `useState`, `useContext`). This affects many popular libraries like **lucide-react**, **heroicons**, and any component that relies on hooks internally.

This fork automatically installs a lightweight mock dispatcher before calling component functions, so hooks return sensible defaults for static rendering:

| Hook | Mock behavior |
|------|--------------|
| `useState(init)` | Returns `[initialValue, noop]` |
| `useContext(ctx)` | Returns the context's default value |
| `useRef(init)` | Returns `{ current: init }` |
| `useMemo(fn)` | Calls and returns `fn()` |
| `useCallback(fn)` | Returns `fn` |
| `useReducer(_, init)` | Returns `[init, noop]` |
| `useEffect` / `useLayoutEffect` | No-op |

The mock is only active during the synchronous execution of each component function (microseconds), then immediately restored. It cannot interfere with React's own rendering in the same project.

```jsx
import { XIcon } from 'lucide-react';

// This now works — no configuration needed
await satori(
  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
    <XIcon size={24} color="red" />
    <span>Close</span>
  </div>,
  { width: 200, height: 50, fonts }
);
```

## Preset Selector

Satori applies default styles to semantic HTML elements (margins on `<p>`, font-sizes on `<h1>`-`<h6>`, etc.). The `preset` option lets you choose which defaults to use:

| Value | Behavior |
|---|---|
| `'chrome'` (default) | Chromium browser defaults — `<p>` has `margin: 1em 0`, headings have font-sizes and `font-weight: bold`, etc. |
| `'tailwind'` | Tailwind preflight reset — no margins on block elements, no heading font-sizes/weights, minimal defaults |

```tsx
const svg = await satori(
  <div style={{ display: 'flex', flexDirection: 'column' }}>
    <h1>No margin, no bold, no 2em font-size</h1>
    <p>No margin</p>
  </div>,
  {
    width: 800,
    height: 400,
    fonts,
    preset: 'tailwind',
  },
)
```

## Tailwind CSS

Satori supports Tailwind CSS class names. Pass `tailwind: true` to enable.

### Installation

`tailwindcss` is an optional peer dependency — install it only if you need className support:

```bash
npm install tailwindcss
```

### Usage

```tsx
const svg = await satori(
  <div className="flex flex-col w-full h-full bg-blue-500 p-8">
    <span className="text-white text-4xl font-bold">Hello World</span>
    <span className="text-white/50 text-xl">Built with Tailwind</span>
  </div>,
  {
    width: 800,
    height: 400,
    fonts,
    tailwind: true,
  },
)
```

The `tailwind` option accepts:

| Value | Behavior |
|---|---|
| `false` / `undefined` | Disabled (default). No overhead, fully backward compatible |
| `true` | Enabled with the default Tailwind v4 theme |
| `string` | Enabled with a custom Tailwind CSS config (full replacement) |
| `(defaultCss: string) => string` | Enabled — receives the default CSS and returns modified CSS |

### Extend Theme

Use the callback form to extend the default theme without redeclaring everything:

```tsx
const svg = await satori(
  <div className="flex w-full h-full bg-brand">Custom</div>,
  {
    width: 800,
    height: 400,
    fonts,
    tailwind: (defaultCss) => {
      return defaultCss + `
        @theme {
          --color-brand: #ff00ff;
          --color-accent: #00ccff;
        }
      `;
    },
  },
)
```

### Custom Theme (Full Replacement)

```tsx
const svg = await satori(
  <div className="flex w-full h-full bg-brand">Custom</div>,
  {
    width: 800,
    height: 400,
    fonts,
    tailwind: `
      @layer theme, base, components, utilities;
      @layer theme {
        @theme {
          --color-brand: #ff00ff;
        }
      }
      @layer utilities {
        @tailwind utilities;
      }
    `,
  },
)
```

### Style Priority

When both `className` and `style` are present, inline styles always take priority:

```tsx
// backgroundColor will be "blue", not the Tailwind red-500 value
<div className="bg-red-500" style={{ backgroundColor: 'blue' }} />
```

## Faux Bold

When only a regular weight font (e.g., 400) is loaded and bold text is requested (e.g., `fontWeight: 700`), Satori automatically synthesizes bold by thickening glyphs with an SVG stroke that matches the fill color. This replicates how browsers handle font synthesis when a bold variant is unavailable.

- Stroke width scales proportionally with the weight difference
- Explicit `-webkit-text-stroke` takes precedence over faux bold

## CSS Enhancements

### `backdrop-filter`

Simulated via SVG filter primitives. Supported functions: `blur`, `brightness`, `contrast`, `grayscale`, `hue-rotate`, `invert`, `opacity`, `saturate`, `sepia`.

Uses `<use>` to reference background content with `feGaussianBlur` and `feOffset` edge-extension for consistent blur without transparent edges. Color-sensitive filters use `color-interpolation-filters="sRGB"` to match browser rendering.

Works with `mask-image` for blurred fades and respects `border-radius` on the clip path.

### `mix-blend-mode`

Full support for all blend modes via SVG `mix-blend-mode`.

### `background-position`

Correct positioning when combined with `background-size: cover`, `contain`, or `auto`. Supports keywords (`center`, `left`, `right`, `top`, `bottom`), percentages, lengths, and mixed values.

## Transform Element

Use `transformElement` to intercept and modify the React element tree before rendering. The hook runs **after** Tailwind CSS resolution (styles are already inlined) and **before** layout calculation.

```jsx
const svg = await satori(
  <div style={{ display: 'flex' }}>
    <div>Hello</div>
    <div>World</div>
  </div>,
  {
    ...,
    transformElement: (element) => {
      // Modify, replace, or filter the element tree
      return element
    },
  },
)
```

Supports async transforms:

```jsx
const svg = await satori(
  <div>hello</div>,
  {
    ...,
    transformElement: async (element) => {
      const data = await fetchData()

      return <div>{data}</div>
    },
  },
)
```

## Overview

Satori supports the JSX syntax, which makes it very straightforward to use. Here’s an overview of the basic usage:

```jsx
// api.jsx
import satori from 'satori'

const svg = await satori(
  <div style={{ color: 'black' }}>hello, world</div>,
  {
    width: 600,
    height: 400,
    fonts: [
      {
        name: 'Roboto',
        // Use `fs` (Node.js only) or `fetch` to read the font as Buffer/ArrayBuffer and provide `data` here.
        data: robotoArrayBuffer,
        weight: 400,
        style: 'normal',
      },
    ],
  },
)
```

Satori will render the element into a 600×400 SVG, and return the SVG string:

```js
'<svg ...><path d="..." fill="black"></path></svg>'
```

Under the hood, it handles layout calculation, font, typography and more, to generate a SVG that matches the exact same HTML and CSS in a browser.

<br/>

## Documentation

### JSX

Satori only accepts JSX elements that are pure and stateless. You can use a subset of HTML
elements (see section below), or custom React components, but React APIs such as `useState`, `useEffect`, `dangerouslySetInnerHTML` are not supported.

#### Experimental: builtin JSX support

Satori has an experimental JSX runtime that you can use without having to install React. You can enable it on a per-file basis with [`@jsxImportSource` pragmas](https://www.typescriptlang.org/tsconfig/#jsxImportSource). In the future, it will autocomplete only the subset of HTML elements and CSS properties that Satori supports for better type-safety.

```tsx
/** @jsxRuntime automatic */
/** @jsxImportSource satori/jsx */

import satori from 'satori';
import { FC, JSXNode } from 'satori/jsx';

const MyComponent: FC<{ children: JSXNode }> = ({ children }) => (
  <div style={{ color: 'black' }}>{children}</div>
)

const svg = await satori(
  <MyComponent>hello, world</MyComponent>,
  options,
)
```

#### Use without JSX

If you don't have JSX transpiler enabled, you can simply pass [React-elements-like objects](https://reactjs.org/docs/introducing-jsx.html) that have `type`, `props.children` and `props.style` (and other properties too) directly:

```js
await satori(
  {
    type: 'div',
    props: {
      children: 'hello, world',
      style: { color: 'black' },
    },
  },
  options
)
```

### HTML Elements

Satori supports a limited subset of HTML and CSS features, due to its special use cases. In general, only these static and visible elements and properties that are implemented.

For example, the `<input>` HTML element, the `cursor` CSS property are not in consideration. And you can't use `<style>` tags or external resources via `<link>` or `<script>`.

Also, Satori does not guarantee that the SVG will 100% match the browser-rendered HTML output since Satori implements its own layout engine based on the [SVG 1.1 spec](https://www.w3.org/TR/SVG11).

You can find the list of supported HTML elements and their preset styles [here](https://github.com/feliperohdee/satori/blob/main/src/handler/presets.ts).

#### Images

You can use `<img>` to embed images. However, `width`, and `height` attributes are recommended to set:

```jsx
await satori(
  <img src="https://picsum.photos/200/300" width={200} height={300} />,
  options
)
```

When using `background-image`, the image will be stretched to fit the element by default if you don't specify the size.

If you want to render the generated SVG to another image format such as PNG, it would be better to use base64 encoded image data (or buffer) directly as `props.src` so no extra I/O is needed in Satori:

```jsx
await satori(
  <img src="data:image/png;base64,..." width={200} height={300} />,
  // Or src={arrayBuffer}, src={buffer}
  options
)
```

### CSS

Satori uses the same Flexbox [layout engine](https://yogalayout.com) as React Native, and it’s **not** a complete CSS implementation. However, it supports a subset of the spec that covers most common CSS features:

<table>
<thead>
<tr>
  <th>Property</th>
  <th>Property Expanded</th>
  <th>Supported Values</th>
  <th>Example</th>
</tr>
</thead>
<tbody>

<tr>
<td colspan="2"><b>CSS Variables</b></td>
<td>Supported, including <code>--var-name</code> declaration and <code>var(--var-name)</code> usage with fallback values</td>
<td><a href="https://og-playground.vercel.app/?share=rVLRTsIwFP2V5hIzTbY4wBjTIC9oos-a8MJLt95tha4lXQfOZf9uOxwRlTeeentO7zntuW0h1RyBwoyL3UoRUtlG4mPb-pqQIIpsgSVGqZbaBJQEnJlNImsMwsOJAkVeWEeM4_hqAPeC2-IXxkW1laxxaCbxY0B9_SQMplZo5TjnU5dqYJkUuXq1WFaeQmXRDNS6rqzImoV2oPL-p3TC0k1udK34wt_c8aMsy46urutNfCIl08kPaPn9lvs47tGuW6m5L3w4x2RIn4VT3DFzfZLPTeBa5i8opQ7JUhvJZ7eu8x-Jv7lqw1TuUr2E-lmJaBKSUTaNx_H4vNqwQgh668dSAW2hHynQBxcNHGYO9M5vOCZ1DjRjssIQsNRr8d5s_Zey-37ndHy4z2WCHKg1NXYhWJa4E4W333tz6L4A">Example</a></td>
</tr>

<tr>
<td colspan="2"><code>display</code></td>
<td><code>flex</code>, <code>contents</code>, <code>none</code>, default to <code>flex</code></td>
<td></td>
</tr>

<tr>
<td colspan="2"><code>position</code></td>
<td><code>relative</code>, <code>static</code> and <code>absolute</code>, default to <code>relative</code></td>
<td></td>
</tr>

<tr>
<td colspan="2"><code>color</code></td>
<td>Supported</td>
<td></td>
</tr>

<tr><td rowspan="5"><code>margin</code></td></tr>
<tr><td><code>marginTop</code></td><td>Supported</td><td></td></tr>
<tr><td><code>marginRight</code></td><td>Supported</td><td></td></tr>
<tr><td><code>marginBottom</code></td><td>Supported</td><td></td></tr>
<tr><td><code>marginLeft</code></td><td>Supported</td><td></td></tr>

<tr><td rowspan="5">Position</td></tr>
<tr><td><code>top</code></td><td>Supported</td><td></td></tr>
<tr><td><code>right</code></td><td>Supported</td><td></td></tr>
<tr><td><code>bottom</code></td><td>Supported</td><td></td></tr>
<tr><td><code>left</code></td><td>Supported</td><td></td></tr>

<tr><td rowspan="3">Size</td></tr>
<tr><td><code>width</code></td><td>Supported</td><td></td></tr>
<tr><td><code>height</code></td><td>Supported</td><td></td></tr>

<tr><td rowspan="5">Min & max size</td></tr>
<tr><td><code>minWidth</code></td><td>Supported except for <code>min-content</code>, <code>max-content</code> and <code>fit-content</code></td><td></td></tr>
<tr><td><code>minHeight</code></td><td>Supported except for <code>min-content</code>, <code>max-content</code> and <code>fit-content</code></td><td></td></tr>
<tr><td><code>maxWidth</code></td><td>Supported except for <code>min-content</code>, <code>max-content</code> and <code>fit-content</code></td><td></td></tr>
<tr><td><code>maxHeight</code></td><td>Supported except for <code>min-content</code>, <code>max-content</code> and <code>fit-content</code></td><td></td></tr>

<tr><td rowspan="5"><code>border</code></td></tr>
<tr><td>Width (<code>borderWidth</code>, <code>borderTopWidth</code>, ...)</td><td>Supported</td><td></td></tr>
<tr><td>Style (<code>borderStyle</code>, <code>borderTopStyle</code>, ...)</td><td><code>solid</code> and <code>dashed</code>, default to <code>solid</code></td><td></td></tr>
<tr><td>Color (<code>borderColor</code>, <code>borderTopColor</code>, ...)</td><td>Supported</td><td></td></tr>
<tr><td>
  Shorthand (<code>border</code>, <code>borderTop</code>, ...)</td><td>Supported, i.e. <code>1px solid gray</code><br/>
</td><td></td></tr>

<tr><td rowspan="6"><code>borderRadius</code></td></tr>
<tr><td><code>borderTopLeftRadius</code></td><td>Supported</td><td></td></tr>
<tr><td><code>borderTopRightRadius</code></td><td>Supported</td><td></td></tr>
<tr><td><code>borderBottomLeftRadius</code></td><td>Supported</td><td></td></tr>
<tr><td><code>borderBottomRightRadius</code></td><td>Supported</td><td></td></tr>
<tr><td>Shorthand</td><td>Supported, i.e. <code>5px</code>, <code>50% / 5px</code></td><td></td></tr>

<tr><td rowspan="11">Flex</td></tr>
<tr><td><code>flexDirection</code></td><td><code>column</code>, <code>row</code>, <code>row-reverse</code>, <code>column-reverse</code>, default to <code>row</code></td><td></td></tr>
<tr><td><code>flexWrap</code></td><td><code>wrap</code>, <code>nowrap</code>, <code>wrap-reverse</code>, default to <code>wrap</code></td><td></td></tr>
<tr><td><code>flexGrow</code></td><td>Supported</td><td></td></tr>
<tr><td><code>flexShrink</code></td><td>Supported</td><td></td></tr>
<tr><td><code>flexBasis</code></td><td>Supported except for <code>auto</code></td><td></td></tr>
<tr><td><code>alignItems</code></td><td><code>stretch</code>, <code>center</code>, <code>flex-start</code>, <code>flex-end</code>, <code>baseline</code>, <code>normal</code>, default to <code>stretch</code></td><td></td></tr>
<tr><td><code>alignContent</code></td><td>Supported</td><td></td></tr>
<tr><td><code>alignSelf</code></td><td>Supported</td><td></td></tr>
<tr><td><code>justifyContent</code></td><td>Supported</td><td></td></tr>
<tr><td><code>gap</code></td><td>Supported</td><td></td></tr>

<tr><td rowspan="5">Font</td></tr>
<tr><td><code>fontFamily</code></td><td>Supported</td><td></td></tr>
<tr><td><code>fontSize</code></td><td>Supported</td><td></td></tr>
<tr><td><code>fontWeight</code></td><td>Supported</td><td></td></tr>
<tr><td><code>fontStyle</code></td><td>Supported</td><td></td></tr>

<tr><td rowspan="13">Text</td></tr>
<tr><td><code>tabSize</code></td><td>Supported</td><td></td></tr>
<tr><td><code>textAlign</code></td><td><code>start</code>, <code>end</code>, <code>left</code>, <code>right</code>, <code>center</code>, <code>justify</code>, default to <code>start</code></td><td></td></tr>
<tr><td><code>textIndent</code></td><td>Supported, including negative values (hanging indent)</td><td></td></tr>
<tr><td><code>textTransform</code></td><td><code>none</code>, <code>lowercase</code>, <code>uppercase</code>, <code>capitalize</code>, defaults to <code>none</code></td><td></td></tr>
<tr><td><code>textOverflow</code></td><td><code>clip</code>, <code>ellipsis</code>, defaults to <code>clip</code></td><td></td></tr>
<tr><td><code>textDecoration</code></td><td>Support line types <code>underline</code> and <code>line-through</code>, and styles <code>dotted</code>, <code>dashed</code>, <code>double</code>, <code>solid</code></td><td><a href="https://og-playground.vercel.app/?share=pVPLTsMwEPwVaytUkAKkPCRklV4oXwDHXhx7YxtcO3Ic2hLl37GTtEKIQynywTvjndGstG6BO4FAYS70x8oSUoedwce2TTUhCrVUgZLpLM_PptlAbrQI6gcndF0ZtotsaXC7Z1O91B550M7GN-5Ms7b714oJoa2kZJaPTMH4u_SuseLJGeejYlKW5cHN2fCiP5GS25uRkqxK8gS6bmUXqUiTHMYgAbdhidx5NmawzuI0di9SMb-OzceoYiT0Ro_SAzpan5ovg4qzSdVbfCf-noIIFwIK4lH0bgM8KQ0RrFbRqjDNMNyAT8rUFAbJhHP-_1CDl5cFO8-z_lzdX_ySb39DBq5KTjXQFvoVBfqQ5xkMOwz0LgGBRSOBlszUmAGu3Zt-3VXpA4RNj6JP2rPndYECaPANdhkEVsQOhca4jfNGQPcF">Example</a></td></tr>
<tr><td><code>textShadow</code></td><td>Supported</td><td></td></tr>
<tr><td><code>lineHeight</code></td><td>Supported</td><td></td></tr>
<tr><td><code>letterSpacing</code></td><td>Supported</td><td></td></tr>
<tr><td><code>whiteSpace</code></td><td><code>normal</code>, <code>pre</code>, <code>pre-wrap</code>, <code>pre-line</code>, <code>nowrap</code>, defaults to <code>normal</code></td><td></td></tr>
<tr><td><code>wordBreak</code></td><td><code>normal</code>, <code>break-all</code>, <code>break-word</code>, <code>keep-all</code>, defaults to <code>normal</code></td><td></td></tr>
<tr><td><code>textWrap</code></td><td><code>wrap</code>, <code>balance</code>, defaults to <code>wrap</code></td><td></td></tr>

<tr><td rowspan="7">Background</td></tr>
<tr><td><code>backgroundColor</code></td><td>Supported, single value</td><td></td></tr>
<tr><td><code>backgroundImage</code></td><td><code>linear-gradient</code>, <code>repeating-linear-gradient</code>, <code>radial-gradient</code>, <code>repeating-radial-gradient</code>, <code>url</code>, single value</td><td></td></tr>
<tr><td><code>backgroundPosition</code></td><td>Supports keywords (<code>center</code>, <code>left</code>, <code>right</code>, <code>top</code>, <code>bottom</code>), percentages, lengths, and mixed values. Works correctly with <code>backgroundSize: cover</code>, <code>contain</code>, and <code>auto</code>.</td><td></td></tr>
<tr><td><code>backgroundSize</code></td><td>Support <code>cover</code>, <code>contain</code>, <code>auto</code>, and two-value sizes i.e. <code>10px 20%</code></td><td><a href="https://og-playground.vercel.app/?share=ZZXXjqNIFIZfpeWb2RW9Itik3tmRwAQDJgeDNTfkHEwwYdTvvnhGWq003HDOqZ8fDlX11Y9D2Ebx4ePwNcqf35u3t2Fcq_ifHz9e8dtbFudpNn68wRD0_qsy59GY_b8Q-GGZ9u3UREbcxf4u_tK0f_U_4y-_acx8i3dF2Dajnze_jwu1n74EU1_9Efmj_5G_CmDXpH8H_hBjp_fcoVVjhiQ-ban9Ukw7Y-10j3j9ldtnyttvND6LubMHTABVrO4YJypeXGSdBtuwwCRWANMRpy7W49jmUFvuyU0fnmZeduAiZ6ZZaueUXM0V5VDBNs2OtfX6MimW0iTTBD0JeYbFeSOrpLGR5_iMkrHGYSAGEFJl77xRCsjNDC_MmIHV8KDOdsboszJAXchEZifrokEtSyrT5cUkbkifODIuyKC2oblXy8yAqyt-VLcJr8UhYJc2PGoWrW4dppebvMnexThypKZD-IRPx2dFjjnj3eAxqMU1XLTipNKFhrv-1Te3ZWE41aKHtHUogVtC98ZlniyqI9lkcOS5xQnRSlTV0-tKaHckalSGADnQlda0i12YNG7wcO3J-IrGTiWLXtviZ6tc1LDpTrmyhe5uybE1CSYwMHinsikaLkdSepmcxdufvxhlwGcYZFBUF9yqXt9fIT5MC0L3H99JUi4UOqz4PPHkFsq72zM8WjYnYGqRWukUlGh4gcFkBi7zyiOuA0c-D4-VW-zWoSnBLSFHV2qkeLMWcwGd7jWpWVCi5AgElPbN7TB1vJpQbagUW4USzTgmUwZs5ckWiKtbeoovGVYKVZYplg37_NjrsGiWVdUBSYO2HADFotULPEJ1kl-3_QMDRpkEjjNQJkq5rpYMXk6gMN14z9Nh9lIhURHsoqlSm5QUTb2sFTs1ag4JjyqDx8eRoJY24GdbW3E7mrfC2jYpulOIfsX9q8znWkYJIU1Lpl3RHbd1jvHq2hVJ27w8Hu0DQtHereC9qSlsH2pBzdY8HbMclOVBaxkCNQPbCS280YnTjDj1vRVdXk2GTcHUtcLIC2kuZmc-F0CjT5O0bxLguUZX0-46gocAOemyO9ud16EQRYoEzoUHKbUT2c6Z6iVo8ikGz_c28GlfMMZFXId-3ndkk55Y6pEpLu00ERw5DiE5ibPw6Jg0pxiXMCDPY5Xv0ooyy07SOY2ItUJA0GcAc95NKKS17ERr91XBFBjo7kTiSi-nM-MjY9_WyXH_6BRO6OxOPK9k9bA925tNd9HmcmRzKI6YXmAFWWNmoOixTTtukLU1GRFeZIuaqTM24QNH063N0Ubq86kAJkdSfl7B89Dt0_KkVXVtMRYgb4Wsogggys-O-89XYZGtuDtasBrKY6ZmsIg42314VQNix3wBqQC70a0ODgxY3BbqKvgnxsscyioJTTAUoJHSrXUiwWwtLqtAakcVCILGBrn4C1p0-WKUzlCXhIAKTFUJTXzV9zXLWaU56fX5_OfvsDy3VdvvsJyzfIy_vEY_P783376CO8u_fW8O74e2G_O2GQ4fPw4_wX34IHZwH35h_fBxeiVRHEzp4SPxqyF-P8R1W-TW2r3OhHH-me0-yc5rtg7i6PAx9lP8-X4Y_WBXZHFVtXPbV9Hh818">Example</a></td></tr>
<tr><td><code>backgroundClip</code></td><td><code>border-box</code>, <code>text</code></td><td></td></tr>
<tr><td><code>backgroundRepeat</code></td><td><code>repeat</code>, <code>repeat-x</code>, <code>repeat-y</code>, <code>no-repeat</code>, defaults to <code>repeat</code></td><td></td></tr>

<tr><td rowspan="5"><code>transform</code></td></tr>
<tr><td>Translate (<code>translate</code>, <code>translateX</code>, <code>translateY</code>)</td><td>Supported</td><td></td></tr>
<tr><td>Rotate</td><td>Supported</td><td></td></tr>
<tr><td>Scale (<code>scale</code>, <code>scaleX</code>, <code>scaleY</code>)</td><td>Supported</td><td></td></tr>
<tr><td>Skew (<code>skew</code>, <code>skewX</code>, <code>skewY</code>)</td><td>Supported</td><td></td></tr>

<tr>
<td colspan="2"><code>transformOrigin</code></td>
<td>Support one-value and two-value syntax (both relative and absolute values)</td>
<td></td>
</tr>

<tr>
<td colspan="2"><code>objectFit</code></td>
<td>Supported</td>
<td><a href="https://og-playground.vercel.app/?share=7VVNj5swEP0ro6mqJFJaslJVVVbYQ6X2F_TIBewBvHVsZMwmEeK_75BAAvsl7WUPq-WC5j0P896zNLQonSIUuFxBfAttYgHyxsqgnYXf7rBsQZbaKE8WutWZB_AUGm9hq_Q91OFoKG4HBmCvVSgF3Gw26xEqSRdlmGNK15VJjwIWuaHD4oJnqfxfeNdYxdSXPM8nlPOKPMM31QFqZ7RiIWpxprvudjzXjoq7M7KNWOeJZaB_DfKXA83s2PrYzMs6L0Z_TEzNrI5gN8i46NtyrpeCS70rrhVr8DJOsAyhqkUU8XBJpTPqu3TRz83mwPOiyhYJTntOWuKW-WHYVE3ccs8Mf2pzYmjB2r9OjU59PUu67I5k-Kt7PtfGDFcyt98_0TWDaBrCh05Eunvyn5HMI7Eh1fZdQ-FMXonkhUTeK5Bapoa-Kbd_aybX3bZKbAeQWFyjq_r1XaNo8aQOxS9eUnhWg6LfWKgoawoUeWpqWiPt3J3-d6z6P0HYnyr-Ts7X9GeXkUIRfEPdGkOa8YmSjHF7543C7gE">Example</a></td>
</tr>

<tr>
<td colspan="2"><code>objectPosition</code></td>
<td>Supports keywords (<code>top</code>, <code>bottom</code>, <code>left</code>, <code>right</code>, <code>center</code>), percentages (e.g., <code>25% 75%</code>), lengths (e.g., <code>10px 20px</code>), and mixed values (e.g., <code>left 20%</code>). Defaults to <code>center</code> (<code>50% 50%</code>).</td>
<td><a href="https://og-playground.vercel.app/?share=7VTBitswEP2VQaUkgbTOQilFxHsotOceevTFlsa2torGyHKTYPzvHSV24rS7Xfayp_XFzHsj6b0nMb1QpFFIsVxBeg995gDKzqlgyMFXOix7ULWx2qODYXXmATyGzjvYavMb2nC0mPYjA7A3OtQS7jab9QTVaKo63GLatI3NjxIWpcXD4oIXufpVeeqcZupdWZYzirxGz_Bdc4CWrNEsRC_O9DDcT339pHg4I9uEdZ5YBuJvlL8caWanpX-beVrnxeinmakbqxM4jDIu-rac66Xg0uyqa8UavEozUYfQtDJJ-HCFNVn9UVHyebM58HlJ46pMzNectKQ98-NhczVpz2tu8H9tzgwtWPv7udG5r0dJKh5Qhe8m8opcyI17vOUHtSa-rNiHLqAfL-82qPgl17SSeVxv2XFfQSHQ7lWz4-j-k9wTwb1Wbq3KLX7QtH8-ukAN-LjtC9O7zpBV5gaAzIm1oCbu2grZi5MPIb_wMBBn3ULGySA0Fl0lZJnbFtcCd_Rgfh6bOHHD_lTxPiXf-7ddgVrI4Dsc1iLkBXfUaC3tyVsthj8">Example</a></td>
</tr>

<tr>
<td colspan="2"><code>opacity</code></td>
<td>Supported</td>
<td></td>
</tr>

<tr>
<td colspan="2"><code>boxSizing</code></td>
<td>Supported</td>
<td></td>
</tr>

<tr>
<td colspan="2"><code>boxShadow</code></td>
<td>Supported</td>
<td></td>
</tr>

<tr>
<td colspan="2"><code>overflow</code></td>
<td><code>visible</code> and <code>hidden</code>, default to <code>visible</code></td>
<td></td>
</tr>

<tr>
<td colspan="2"><code>filter</code></td>
<td>Supported</td>
<td></td>
</tr>

<tr>
<td colspan="2"><code>backdropFilter</code></td>
<td>Supported (<code>blur</code>, <code>saturate</code>, <code>brightness</code>, <code>contrast</code>, <code>grayscale</code>). Works with <code>maskImage</code> for faded blur effects</td>
<td></td>
</tr>

<tr>
<td colspan="2"><code>mixBlendMode</code></td>
<td>Supported</td>
<td></td>
</tr>

<tr>
<td colspan="2"><code>clipPath</code></td>
<td>Supported</td>
<td><a href="https://og-playground.vercel.app/?share=XVJNb9wgEP0rI6poW8lJnX6pstpe0h7aQ1UlrXLJBZvBZosZBDgbZ7X_PQMbZze5wPCGmXmPx1Z0pFA04osytzcOIKbZ4tftNscAA5p-SA2szuv6ZFXtwY1RaXiBKRO9lTOj2uLdgub4uwnYJUOOcx3ZaXRLVlrTu58Jx5hT6BKGJbWeYjJ6viAGXZ7_PN3K7n8faHLqgiwFzr_SWj9N5aorc48NvH93BF0_avlU1wXd7W7ctxws0l-KP8j_8FhypP4Y8lIp4_oGzg_YgSKzY6FDau2EC0WAzhr_R5Z39GTnntzrj_UJ1BU34Z3jKi_lVEGd4zerfXEmDlCoA_yLqKCdIdKIQBrSgLChYNUqgpWhx5igo9FLZzBW8Bvv0tk6AjrZWoww0wSJoAsoE4KerD2NianDNbYgvbemk9m8mGdwLbqstEyxXMHNL1F2CTTXTyFPkE6BYbP6wIV81dMGAzeGS_b0tJWZ7y95K6-6YHzi4WTzNU2hdNUylrbtZKyKZ8Wft2wQy112UQnyhZRotqL4IZrP7IfY-yWabI5Q2E69aLS0ESuBI63N39nnv5425cR98r_4MbaoRJPChLtKJNnyjQGtpfKMYvcA">Example</a></td>
</tr>

<tr>
<td colspan="2"><code>lineClamp</code></td>
<td>Supported</td>
<td><a href="https://og-playground.vercel.app/?share=5VPBbtQwEP2VkRFakNKSshxQBBwoXDhwaEFc9uLYk6xbx2PZk-6G1Up8DR_GlzDOkgr13FtPGb_xvPf8ojkoQxZVo95Zd7cJAJknj-8Ph1IDbNH1W25gdVHXz1fVCdw5y9sHmHU5ej0J2nncL2ipP7mEhh0F6Rny4xCWbtTWutA3cFH_Q1ptbvtEY7CX5CnJxLOu6-7ZKPC1-4kNrF_P0PG4CR9KsZh_aP9_X60nc7tQAXgX8NLrIQrbPTjo1LvwkZhpkJF1HferU69IAcxiAN8zWmgnyDQgUAe8RdhR8naVwQsFZgZDQ9TBYa7gK-75_CYDBt16zDDRCExgEmpG6EbvzzLLy-EHtqBj9M7oElguGjKLocQ0q3iZEPIr1Iahk_kxFQUdLLjA2CcZlKuRdpiEGK7GzGetLn6_6Dt9bZKLLOIkz-8l0DSzdjrPtO3ovM3nc6KvJNJHyHa1ho368-s3vDBihQb5fVayEa-BX27UE093-apKUZxNqeag5v1Szdu6rtRpAVXzphwstmOvmk77jJXCgW7ctymW7eXdfBKesiSfhxatajiNeKwU61ZubNF7mmNUx78">Example</a></td>
</tr>

<tr><td rowspan="5">Mask</td></tr>
<tr><td><code>maskImage</code></td><td><code>linear-gradient(...)</code>, <code>radial-gradient(...)</code>, <code>url(...)</code></td><td><a href="https://og-playground.vercel.app/?share=pZJfb9MwFMW_imVp2ZDS5s_I1kULSMAkhgRoYlJf-uLYN8ltHTvYDm2o-t2xuxXBXvcQXed3rONj37unXAugJb0V-GulCLFuklDt92FNSAfYdq4k51manp3HT3CLwnUvmEA7SDZ52kjYnWhYf0ID3KFWXuNajr06qUxiq-4d9DZIoByYk7QercNm-qg9VOH8_-XG8x_4G0pymf-Dls9pr9L0mdaMb1qjRyW8x2jkRefcYMskwZ61YOejCrFtN-e6T4ZOOz3LinyRL65v3ubZdTZrari8KkQmbhh_jzuJdWXqWTbP51n0s1oUUdNX66GNuNFD5TP6MkXbKsvTNOK2sqatI9yhqGD60vHPHxq2fMDv67v022NbNA9vTjfqmd3ch0w-p2ECmZy1oXrLC46GSyDMkSI9C19MajlCTJxhPj8zftNfoyXUG3RfX21HkuTYBf-whhhowGMOBBXpXC_DWYfDSr1bqdvET46vNKZ6CH22tNzT44zQMrxDTJ-miJahL1RAPba0bJi0EFPo9RofpyGMoNse_7xRaOZdX4OgpTMjHGLqWO13dCCl3mojBT38AQ">Example</a></td></tr>
<tr><td><code>maskPosition</code></td><td>Supported</td><td><a href="https://og-playground.vercel.app/?share=pVJda9swFP0rQlC3Ayf-6NKmpt5gW2Ed7KOskJe8yNK1fRNZ8iR5iRfy3yelCayFPfXBvtK5h3uP7j07yrUAWtBbgb-XihDrRgnlbhfOhLSATesKcp6l6dl5_ARuULj2BSbQ9pKNHq0lbE9oOH9CA9yhVj7HtRw6dcoyiY26d9DZkALlwJxSq8E6rMeP2oMq9H-erj3-E_9AQS7zf6DFUe1Vmh7RivF1Y_SghK8xGHnROtfbIkmwYw3Y6aCCbNtOue6SvtVOT7JZPs_n1zdv8-w6m9QVXF7NRCZuGH-PW4lVaapJNs2nWfSrnM-iuitXfRNxo_vSa_RhjDZllqdpxG1pTVNFuEVRwvil5Z8_1GzxgN9Xd-m3x2ZWP7w5vahjdn0fNHmdhglkctKE6EtecDRcAmGOzNKz8MWkkgPExBnm9TPjSc8K_dAWjxP3O-q35PA_MRZQrdF9fX1DkiSHRfnZG2KgBo9zIKhI6zr5stl_RAXafr9U75bqNvEe9JHGVPeBammxowe30SJMNKZPfqRF2DAVUA0NLWomLcQUOr3Cx7EPZnabw80XCra46yoQtHBmgH1MHas8owUp9UYbKej-Lw">Example</a></td></tr>
<tr><td><code>maskSize</code></td><td>Support two-value size i.e. <code>10px 20%</code></td><td><a href="https://og-playground.vercel.app/?share=pVLfb9MwEP5XLEvLhpQ2P0a3LlpAAiYxJEATk_rSF8e-JNc6drAd2lD1f8duV8H6yoN19ved7j7ffTvKtQBa0HuBv5aKEOtGCeVuF-6EtIBN6wpymaXpxWV8BDcoXHuGCbS9ZKNHawnbExrun9AAd6iV57iWQ6dOLJPYqEcHnQ0UKAfmRK0G67AeP2oPqtD_NV17_Af-hoJc5_9Aixe1N2n6glaMrxujByV8jcHIq9a53hZJgh1rwE4HFWTbdsp1l_StdnqSzfJ5Pr-9e5tnt9mkruD6ZiYyccf4e9xKrEpTTbJpPs2in-V8FtVdueqbiBvdl16jD2O0KbM8TSNuS2uaKsItihLGLy3__KFmiyf8vnpIvz03s_rpzelHHbPrx6DJ6zRMIJOTJkRf8oqj4RIIc2SWXoQTk0oOEBNnmNfPjE96Veg4Gr-ffkvyvztaQLVG9_X_O5EkOWzID90QAzV4nANBRVrXyfNm52oCv98v1buluk-863ykMdV98IilxY4e_EWLMMOYHh1Ii7BTKqAaGlrUTFqIKXR6hc9jH-zrNoeXLxSM8NBVIGjhzAD7mDpW-YwWpNQbbaSg-z8">Example</a></td></tr>
<tr><td><code>maskRepeat</code></td><td><code>repeat</code>, <code>repeat-x</code>, <code>repeat-y</code>, <code>no-repeat</code>, defaults to <code>repeat</code></td><td><a href="https://og-playground.vercel.app/?share=nVbpjqNIEn6VkqXVzMg1AhtjQ-3MStwGA-Ywl9U_hssJ5jSHAbf63TdxdfXUzh4_FhllHF8cGZkm4usirKJ48bb4LUrvX8qXl7ab8vj3r19n-uUliVOQdG8vP61Q9G8_vb4LhzTqkr_IorStc3-C0ksejx_SmWbTJg67tCqhLqzyvig_tH6eglLs4qKdVXHZxc2H6tq3XXqZmAoKyzn-v6ovUG6mj_jtBVt_Ejnfs92i6Hdp4IcZaKq-jKCPvsl_jvzOf0sLH8RIXYK_B34bbzevqU0fjQE9CKCi4KOaVsJZAFK0PvMaQ3lwYbPiSNqzgHJV00BFqmk34XaGiEbucHlxslDqMNtRAJpyJkUpM0NTFAcXzqco6ztPUSbggs-8BTgY_AMvwl9UU9Qz_lPvPP0-WaiEz6zinkKoPwLIs9_lkGcATGEO-o6jTUANn3iKeJJ2F-6CJ58PJp8_ICFzA7QeFZqSbqHwBOW1zSeow62UY6HeAxNPzgKZnk18E7jfU2LHzbFMulBY5ZHAgVhYtUGpbGMWTT3HuHuFtZ35wLFRzyRScQ-2EDNEQkuKeaJaDM0GmJSLrNcrzGYQr5uDyFBA20vZ-VqbBuf98BkWRqGZUhXtjeGYEvcIizC5DB9yQU7niRiPpwyXH9QkP8RJdqF9unrEDo56Luig_fXD9yf_3NlVr2GRw3zye5DS01nwtp4j3SNXJ8VU_IH_eD9ygfjifEVTf2-gIVvd5TUO8-CzYC3l8rNWJOo750J-cHBfRKqB6rMf4t2-1mDsPCiN5Bn_uhk15t3umJGT79h9JPCQJ_tP9oSM_YfcP-rGwFrAPViZIUAbiH2v97P-p81BsHcJDc-ZWvGSwfHWkRZUm-8UDuWsMsJM7ITXvl8YYxpVaWIcDFuQMp-lDYZXUyUzNVLX4KYTMBgGNxwFaEUoF84QWe6mXMYz13GVeU91ISF0sPF8oDNpbYtxHVWOmSxRzOXLyHCzEenHcHk5as7liJUdRl2SjhWUE3PZ2gdM43nDPrEUGwnJxHISvL6WuHksd8qjuK66bTDEQyKKHu5qIGNFxvAo5byyizvKZ1y7x28hf-CjZXAw2EGzWn_bP04b2lbtMx3Y0jpXaDNktVtXbPH7o8A3k8q5jhVNxsBWhe0F8jqqYqJnr17XAO9o1UZrAcDRo3tY7Zc5wGhikM4Wilz3YTH1akMOy1XMMGOVEZokexWWlo7HMrJ4mBzVqM7u8aSyN7SWb870cAKdUCqJ6c2z3xr6gV61QqrD22-O_pRosq3iHQHGzFRQ5Yisjo61Tseq0VByWxKmdVrHa9fRk122bBpiUHnDCTG_OR28OudwcW0E4qGozUaOdnHZRLesiUbXkbg1bku7lGdSrNTYBlHqgxtVdr9RjvZyd8xXd4BUYBvou7VvXLoxGkldoSrXdvmzRHnurQiLi9WfDPsgi_dJE5cB4pIZG6gaVm1qkrxQVSUqZU3d0bDMtxfdU3kSiVwJKVrPXy8HaWAJzNBDjq4entTprFOeWL32HNwX1u2dXVfSQJQeeyMecFNHn6ezUUmaO9md4uaKL3twybpTNuQ1QYGym8gitAjRu7DoFaQkWHI7miNzZ2UUsuEE2LlmjNrpp5XTh3mM-eXt_mi2O1kor9tBsfwbxgILhzkJ68PRPh25nlhRkxTTuWQq6dWVpp3BVPWq6ree34P7ivDPAljuwsQbB8fxjnTSn-lsx7sPMdwQe48zB3lI0rI02Vgz2kBmsColkI3M3QuwOSUEzZuCd99fNIykkP0YINFwL6LNUiQmKiAPoo5PqkVFGc1fEaQtxrW6psdO4C5IhRxC1916J3mrxeDerWKcRC_0etLjCiR7IexI2eG0w57VB4S0zd53hq0xZI7sNuqZ2ka4KhmHvBQke2sbO_gn013TtlADlAmPyZEcSqsdVrqFl8IbmAgqnY9pFukjifCFN1mcGPOEFPsIAi-V0rgqfdMZSlIkpEGa4ua3J44SQ4thMqQRr8ujjuxUKgCIq3Iaheg6fbOvF2qN895725IMC-eaTAIA_P77Lx_NvfDbTJw79NvLH3laxn7zK2j8KIUTwM9d9dLMPf71Jcj7-PWla_yyrf0G6n7545P9-3AwTyj1-LJaz8tn90Zcx_48VjRP4tcfSicOsrRTPmXwl6GhvYPlWOQfg4O2V9fnicZ8x0B92OyOLDWIKV2dnbz097B5XMGgMCKICnsK1_MHGk0VczNCzBp-2DG9IDeaKQ4iSwHlJEIsNSrXp49N4Ix9-PjUXGCjiyYcUyb8HhbhfcYpDPmIijDVruPguUYlCjBmho4KMzxUk6Yhpn2-zDDILNeqJ0g_LKDDWDI7v0_dKLMZpJWVyHM45NeqOazgikMfhgJt1KvVzuts66QiOBd5G8D9RuukjgQrFRliCZvOnMvyx0H8ezH_n-P808v_ONQ_Qf_laL99-1L-40v5GwLHXLguXhdVPQ-l7eLt6-I50C7eZpevi_eRd_E2D5GLKA56sHi7-Hkbvy7iorqmp6me5-VueHLQ0Tx5ckUQR4u3runjb6-Lzg8gIonzvBqqJo8W3_4J">Example</a></td></tr>

<tr>
<td rowspan="2"><code>WebkitTextStroke</code>
<td><code>WebkitTextStrokeWidth</code></td>
<td>Supported</td>
<td></td>
</tr>
<tr>
<td><code>WebkitTextStrokeColor</code></td>
<td>Supported</td>
<td></td>
</tr>

</tbody>
</table>

Note:

1. Three-dimensional transforms are not supported.
2. There is no `z-index` support in SVG. Elements that come later in the document will be painted on top.
3. `calc` isn't supported.
4. `currentColor` support is only available for the `color` property.
5. CSS variables (custom properties) are supported, including inheritance, fallback values, and nested variables.

### Language and Typography

Advanced typography features such as kerning, ligatures and other OpenType features are not currently supported.

RTL languages are not supported either.

#### Fonts

See the [Fonts API](#fonts-api) section for complete documentation on static fonts, dynamic font loading, aliases, and missing glyph handling.

#### Emojis

To render custom images for specific graphemes, you can use `graphemeImages` option to map the grapheme to an image source:

```jsx
await satori(
  <div>Next.js is 🤯!</div>,
  {
    ...,
    graphemeImages: {
      '🤯': 'https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/svg/1f92f.svg',
    },
  }
)
```

The image will be resized to the current font-size (both width and height) as a square.

#### Locales

Satori supports rendering text in different locales. You can specify the supported locales via the `lang` attribute:

```jsx
await satori(
  <div lang="ja-JP">骨</div>
)
```

Same characters can be rendered differently in different locales, you can specify the locale when necessary to force it to render with a specific font and locale. Check out [this example](https://og-playground.vercel.app/?share=nVLdSsMwFH6VcEC86VgdXoyweTMVpyiCA296kzWnbWaalCZ160rfwAcRH8Bn0rcwWVdQEYTdnJzz_ZyEnNNArDkChQkXz5EixNha4rRpfE4IF6aQrKbkOJG4OQ461OfnosTYCq0cF2tZ5apnMxRpZh18EoZHPbgW3Ga_sIJxLlS6Q4sNGbnQU0yKVM0t5sa3R2Wx7KlVZaxI6pl2oPLX_KQTh1-yXEj_6LlnAhLBLXOJYJLMY61MBN_VD2KLlIzGe2jJ4qe01JXiMy116bqsM2Gxc7Stj2edcmIKpohkKp1GsGKD6_sI9hQhn2-vHy_ve-HQK_9ybbPB7O4Q1-LxENfVzX-uydDtgTshAF348RqgDeymB3QchgF04wV66guOyyoFmjBpMADM9Uos6sLvk13vKtfH__FFvkQO1JYVtu0X) to learn more.

Supported locales are exported as the `Locale` enum type.

### Runtime Support

Satori can be directly used in browser, Node.js (>= 16), and Web Workers. It bundles its underlying WASM dependencies as base64-encoded strings and loads them at runtime.

If there is a limitation on dynamically loading WASM (e.g. Cloudflare Workers), you can use the Standalone Build which is mentioned below.

#### Standalone Build of Satori

Satori's standalone build doesn't include Yoga's WASM binary by default, and you need to load it manually before using Satori.

First, you need to download the `yoga.wasm` binary from [Satori build](https://unpkg.com/satori/) and provide it yourself. Let's use `fetch` to load it directly from the CDN as an example:

```jsx
import satori, { init } from 'satori/standalone'

const res = await fetch('https://unpkg.com/satori/yoga.wasm')
const yogaWasm = await res.arrayBuffer()

await init(yogaWasm)

// Now you can use satori as usual
const svg = await satori(...)
```

Of course, you can also load the `yoga.wasm` file from your local disk via `fs.readFile` in Node.js or other methods.

### Font Embedding

By default, Satori renders the text as `<path>` in SVG, instead of `<text>`. That means it embeds the font path data as inlined information, so succeeding processes (e.g. render the SVG on another platform) don’t need to deal with font files anymore.

You can turn off this behavior by setting `embedFont` to `false`, and Satori will use `<text>` instead:

```jsx
const svg = await satori(
  <div style={{ color: 'black' }}>hello, world</div>,
  {
    ...,
    embedFont: false,
  },
)
```

### Pixel Grid Rounding

Set `pointScaleFactor` to control how layout values are rounded to the pixel grid. This parameter is passed directly to [Yoga’s `pointScaleFactor`](https://www.yogalayout.dev/docs/getting-started/configuring-yoga#point-scale-factor) and improves rendering precision on high-DPI displays.

```jsx
const svg = await satori(
  <div style={{ color: 'black' }}>hello, world</div>,
  {
    ...,
    pointScaleFactor: 2,
  },
)
```

### Debug

To draw the bounding box for debugging, you can pass `debug: true` as an option:

```jsx
const svg = await satori(
  <div style={{ color: 'black' }}>hello, world</div>,
  {
    ...,
    debug: true,
  },
)
```