Skip to content
This page is available as Markdown at /rendering/react.md. For the full documentation index, see /llms.txt, or the complete corpus at /llms-full.txt.

React

Terminal window
npm i @portabletext/react

Pass your Portable Text value to the PortableText component. The library handles blocks, marks, lists, and inline content with sensible defaults.

import {PortableText} from '@portabletext/react'
export default function MyComponent({value}) {
return <PortableText value={value} />
}

The value prop accepts a single block object or an array of block objects.

Override the default rendering by passing a components prop. Each key in the object maps to a part of the Portable Text structure.

Marks are inline annotations: bold, italic, links, and any custom annotations you define. Override them under marks.

This example adds safe rel attributes to external links:

const components = {
marks: {
link: ({children, value}) => {
const rel = !value.href.startsWith('/')
? 'noreferrer noopener'
: undefined
return (
<a href={value.href} rel={rel}>
{children}
</a>
)
},
},
}

Pass the components object to PortableText:

<PortableText value={value} components={components} />

Custom block type (image with @sanity/image-url)

Section titled “Custom block type (image with @sanity/image-url)”

Custom block types (objects embedded in your content) are handled under types. Each component receives the block's value and an isInline boolean indicating whether it appears inline or as a standalone block.

import {getImageDimensions} from '@sanity/asset-utils'
import {createImageUrlBuilder} from '@sanity/image-url'
// Initialize with your Sanity project config
const builder = createImageUrlBuilder({
projectId: 'yourProjectId',
dataset: 'production',
})
const urlFor = (source) => builder.image(source)
const SampleImageComponent = ({value, isInline}) => {
const {width, height} = getImageDimensions(value)
return (
<img
src={urlFor(value)
.width(isInline ? 100 : 800)
.fit('max')
.auto('format')
.url()}
alt={value.alt || ' '}
loading="lazy"
style={{
display: isInline ? 'inline-block' : 'block',
aspectRatio: width / height,
}}
/>
)
}

Register it under the type name you used in your schema:

const components = {
types: {
image: SampleImageComponent,
},
}

You can define types, marks, and block styles in a single components object:

const components = {
types: {
image: ({value}) => <img src={value.imageUrl} />,
callToAction: ({value, isInline}) =>
isInline ? (
<a href={value.url}>{value.text}</a>
) : (
<div className="callToAction">{value.text}</div>
),
},
marks: {
link: ({children, value}) => {
const rel = !value.href.startsWith('/')
? 'noreferrer noopener'
: undefined
return (
<a href={value.href} rel={rel}>
{children}
</a>
)
},
},
block: {
h1: ({children}) => <h1 className="text-2xl">{children}</h1>,
blockquote: ({children}) => (
<blockquote className="border-l-purple-500">{children}</blockquote>
),
},
}

Import PortableTextComponents to type your components object. This gives you autocomplete on component keys and typed props for each renderer.

import {PortableText, PortableTextComponents} from '@portabletext/react'
const components: PortableTextComponents = {
marks: {
link: ({value, children}) => {
const target = (value?.href || '').startsWith('http')
? '_blank'
: undefined
return (
<a
href={value?.href}
target={target}
rel={target === '_blank' ? 'noindex nofollow' : undefined}
>
{children}
</a>
)
},
},
block: {
normal: ({children}) => <p className="text-base">{children}</p>,
h1: ({children}) => <h1 className="text-2xl">{children}</h1>,
},
}
export default function Article({value}) {
return <PortableText value={value} components={components} />
}

The components prop accepts the following keys. Any key you omit falls back to the default renderer.

| Key | What it renders | | ------------------- | ---------------------------------------------------------------------------------------- | | types | Custom block types and inline object types. Keyed by _type value. | | marks | Inline annotations (links, custom decorators). Keyed by mark type name. | | block | Standard text blocks. Keyed by style value (normal, h1, h2, blockquote, etc.). | | list | List wrappers. Keyed by list type (bullet, number). | | listItem | Individual list items. Keyed by list type. | | hardBreak | Line breaks within a block (rendered as <br /> by default). | | unknownMark | Fallback for mark types with no matching component. | | unknownType | Fallback for block types with no matching component. | | unknownBlockStyle | Fallback for block styles with no matching component. | | unknownList | Fallback for list types with no matching component. | | unknownListItem | Fallback for list item types with no matching component. |

The unknown* keys are useful for debugging: render a visible warning so you can spot unhandled types during development.

Full API reference, changelog, and additional examples: @portabletext/react on GitHub.