Skip to content

Vue

Terminal window
npm i @portabletext/vue

Pass your Portable Text value to the PortableText component using the :value prop.

<script setup>
import { PortableText } from '@portabletext/vue'
defineProps(['value'])
</script>
<template>
<PortableText :value="value" />
</template>

The component renders standard block types (paragraphs, headings, lists, blockquotes) out of the box.

Pass a components object to the :components prop to override how specific node types are rendered. The prop names are plural: types, marks, block, list, listItem.

For simple overrides, you can define components inline using Vue’s h() function. The render function receives props as the first argument and the render context (including slots) as the second.

Use slots.default?.() to render child content inside block and mark components.

<script setup>
import { PortableText } from '@portabletext/vue'
import { h } from 'vue'
defineProps(['value'])
const components = {
types: {
image: ({ value, isInline }) =>
h('img', {
src: value.imageUrl,
style: { display: isInline ? 'inline-block' : 'block' },
}),
},
marks: {
link: ({ value }, { slots }) => {
const target = (value?.href || '').startsWith('http') ? '_blank' : undefined
return h(
'a',
{ href: value?.href, target, rel: target === '_blank' ? 'noindex nofollow' : undefined },
slots.default?.()
)
},
},
block: {
h1: (_, { slots }) => h('h1', { class: 'text-2xl' }, slots.default?.()),
blockquote: (_, { slots }) =>
h('blockquote', { class: 'border-l-purple-500' }, slots.default?.()),
},
}
</script>
<template>
<PortableText :value="value" :components="components" />
</template>

For more complex components, write a standard Vue single-file component and register it in the components map.

Use PortableTextComponentProps<T> to type the props. The generic parameter T is the shape of your custom block’s value field.

MyButton.vue
<script setup lang="ts">
import type { PortableTextComponentProps } from '@portabletext/vue'
const { value, index } = defineProps<PortableTextComponentProps<{ text: string }>>()
</script>
<template>
<button class="my-button">{{ index }}: {{ value.text }}</button>
</template>

Then import and register it:

<script setup>
import { PortableText } from '@portabletext/vue'
import MyButton from './MyButton.vue'
defineProps(['value'])
const components = {
types: {
button: MyButton,
},
}
</script>
<template>
<PortableText :value="value" :components="components" />
</template>
PatternNotes
<script setup>Recommended composition API syntax. Works with defineProps and defineEmits.
h() render functionsUse Vue’s h() for inline component definitions without a template block.
slots.default?.()Access child content in render functions via the second argument’s slots object.
PortableTextComponentProps<T>Generic type for strongly-typed custom component props. Import from @portabletext/vue.
toPlainText()Extracts plain text from a Portable Text value. Useful for meta descriptions and other non-HTML contexts.
import {toPlainText} from '@portabletext/vue'
useHead({
meta: [{name: 'description', content: toPlainText(myPortableTextData)}],
})

For the complete API reference, all component override keys, and migration notes, see the @portabletext/vue repository on GitHub.