> For the complete documentation index, see [llms.txt](/llms.txt).
> The full corpus is at [llms-full.txt](/llms-full.txt).

# Customize editor rendering

> Change the way the editor renders and styles text.

The Portable Text Editor gives you control of how it renders each schema type element. You need to explicitly tell it what. These choices have no impact on the Portable Text output—they only affect how the editor itself renders content.

:::note[Prerequisites]
This guide covers `@portabletext/editor` **v6.x** ([changelog](https://github.com/portabletext/editor/releases)). Requires React 18+.
:::

The following props can be passed to the `PortableTextEditable` component:

- `renderAnnotation`: For annotations (e.g., hyperlinks).
- `renderBlock`: For block objects (e.g., images, embeds).
- `renderChild`: For inline objects (e.g., custom emoji, stock symbols).
- `renderDecorator`: For decorators (e.g., strong, italic, emphasis text).
- `renderStyle`: For core text block types (e.g., normal, h1, h2, h3, blockquote).
- `renderListItem`: For list item styling (e.g., bullet, numbered lists).
- `renderPlaceholder`: For custom placeholder text when the editor is empty.
- `rangeDecorations`: For highlighting specific ranges of text (e.g., search results, comments).

All the different render functions passed to `PortableTextEditable` can be defined as stand-alone React components.

Most follow the same pattern of reading `props` and conditionally rendering elements based on schema data.

Lists are a bit unique. Portable Text has no concept of block nesting, so the solution is to use pure CSS to style them. We suggest [including this example CSS](https://github.com/portabletext/editor/blob/main/examples/basic/src/editor.css) or similar to manage list rendering.

Here are basic implementations of some core types:

```tsx
const renderDecorator: RenderDecoratorFunction = (props) => {
  if (props.value === 'strong') {
    return <strong>{props.children}</strong>
  }
  if (props.value === 'em') {
    return <em>{props.children}</em>
  }
  if (props.value === 'underline') {
    return <u>{props.children}</u>
  }
  return <>{props.children}</>
}

// Annotations
const renderAnnotation: RenderAnnotationFunction = (props) => {
  if (props.schemaType.name === 'link') {
    return <span style={{textDecoration: 'underline'}}>{props.children}</span>
  }

  return <>{props.children}</>
}

// Block objects
const renderBlock: RenderBlockFunction = (props) => {
  if (props.schemaType.name === 'image' && isImage(props.value)) {
    return (
      <div
        style={{
          border: '1px dotted grey',
          padding: '0.25em',
          marginBlockEnd: '0.25em',
        }}
      >
        IMG: {props.value.src}
      </div>
    )
  }

  return <div style={{marginBlockEnd: '0.25em'}}>{props.children}</div>
}

// Check the shape of an image and confirm it has a src.
function isImage(
  props: PortableTextBlock,
): props is PortableTextBlock & {src: string} {
  return 'src' in props
}

// Styles
const renderStyle: RenderStyleFunction = (props) => {
  if (props.schemaType.value === 'h1') {
    return <h1>{props.children}</h1>
  }
  if (props.schemaType.value === 'h2') {
    return <h2>{props.children}</h2>
  }
  if (props.schemaType.value === 'h3') {
    return <h3>{props.children}</h3>
  }
  if (props.schemaType.value === 'blockquote') {
    return <blockquote>{props.children}</blockquote>
  }
  return <>{props.children}</>
}

// Inline objects
const renderChild: RenderChildFunction = (props) => {
  if (props.schemaType.name === 'stock-ticker' && isStockTicker(props.value)) {
    return (
      <span
        style={{
          border: '1px dotted grey',
          padding: '0.15em',
        }}
      >
        {props.value.symbol}
      </span>
    )
  }

  return <>{props.children}</>
}

// Check the shape of the object by confirming it has a symbol.
function isStockTicker(
  props: PortableTextChild,
): props is PortableTextChild & {symbol: string} {
  return 'symbol' in props
}

// List items
const renderListItem: RenderListItemFunction = (props) => {
  return <>{props.children}</>
}
```

:::note
List items in Portable Text don't nest like HTML lists. The `renderListItem` function wraps the content, but visual nesting is achieved through CSS. See the [example CSS](https://github.com/portabletext/editor/blob/main/examples/basic/src/editor.css) for list styling patterns.
:::

## Placeholder text

Use `renderPlaceholder` to display custom placeholder text when the editor is empty:

```tsx
<PortableTextEditable
  renderPlaceholder={() => <span style={{color: '#999'}}>Start typing...</span>}
  // ... other props
/>
```

## Range decorations

Use `rangeDecorations` to highlight specific ranges of text. This is useful for features like search highlighting, comments, or collaborative cursors:

```tsx
import type {RangeDecoration} from '@portabletext/editor'

const decorations: RangeDecoration[] = [
  {
    selection: {
      anchor: {path: [{_key: 'block1'}, 'children', {_key: 'span1'}], offset: 0},
      focus: {path: [{_key: 'block1'}, 'children', {_key: 'span1'}], offset: 5},
    },
    component: ({children}) => (
      <span style={{backgroundColor: 'yellow'}}>{children}</span>
    ),
  },
]

<PortableTextEditable
  rangeDecorations={decorations}
  // ... other props
/>
```

You can apply styles, libraries like Tailwind, or use custom react components within the rendering functions.