{"id":18471666,"url":"https://github.com/portabletext/vue-portabletext","last_synced_at":"2025-04-07T07:03:56.637Z","repository":{"id":189142679,"uuid":"680140683","full_name":"portabletext/vue-portabletext","owner":"portabletext","description":"Render Portable Text with Vue","archived":false,"fork":false,"pushed_at":"2025-02-06T13:51:36.000Z","size":343,"stargazers_count":35,"open_issues_count":4,"forks_count":0,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-03-31T06:03:58.843Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/portabletext.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-18T12:45:09.000Z","updated_at":"2025-03-16T07:33:54.000Z","dependencies_parsed_at":"2023-08-18T13:11:47.843Z","dependency_job_id":"2945cc4c-7950-4b8a-87e5-2111afbb36c8","html_url":"https://github.com/portabletext/vue-portabletext","commit_stats":null,"previous_names":["portabletext/vue-portabletext"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/portabletext%2Fvue-portabletext","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/portabletext%2Fvue-portabletext/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/portabletext%2Fvue-portabletext/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/portabletext%2Fvue-portabletext/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/portabletext","download_url":"https://codeload.github.com/portabletext/vue-portabletext/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247608150,"owners_count":20965952,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-06T10:17:39.360Z","updated_at":"2025-04-07T07:03:56.610Z","avatar_url":"https://github.com/portabletext.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @portabletext/vue\n\n[![npm version](https://img.shields.io/npm/v/@portabletext/vue.svg?style=flat-square)](https://www.npmjs.com/package/@portabletext/vue)[![npm bundle size](https://img.shields.io/bundlephobia/minzip/@portabletext/vue?style=flat-square)](https://bundlephobia.com/result?p=@portabletext/vue)\n\nRender [Portable Text](https://portabletext.org/) with Vue.\n\nMigrating from [sanity-blocks-vue-component](https://www.npmjs.com/package/rdunk/sanity-blocks-vue-component)? Refer to the [migration docs](https://github.com/portabletext/vue-portabletext/blob/main/MIGRATING.md).\n\nNote that for terseness, [render functions](https://vuejs.org/guide/extras/render-function.html) are used for many of examples below for simple elements, but single file components or JSX can also be used just as easily.\n\n## Table of contents\n\n- [Installation](#installation)\n- [Basic usage](#basic-usage)\n- [Styling](#styling-the-output)\n- [Customizing components](#customizing-components)\n- [Single file components](#single-file-components)\n  - [Defining props with TypeScript](#defining-props-with-typescript)\n- [Available components](#available-components)\n  - [types](#types)\n  - [marks](#marks)\n  - [block](#block)\n  - [list](#list)\n  - [listItem](#listItem)\n  - [hardBreak](#hardBreak)\n  - [unknown components](#unknownMark)\n- [Disabling warnings / handling unknown types](#disabling-warnings--handling-unknown-types)\n- [Rendering Plain Text](#rendering-plain-text)\n- [Typing Portable Text](#typing-portable-text)\n\n## Installation\n\n```\nnpm install --save @portabletext/vue\n```\n\n## Basic usage\n\n```vue\n\u003cscript setup\u003e\nimport { PortableText } from '@portabletext/vue';\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cPortableText\n    :value=\"[\n      /* array of portable text blocks */\n    ]\"\n    :components=\"{\n      /* optional object of custom components to use */\n    }\"\n  /\u003e\n\u003c/template\u003e\n```\n\n## Styling the output\n\nThe rendered HTML does not have any styling applied, so you will either render a parent container with a class name you can target in your CSS, or pass [custom components](#customizing-components) if you want to control the direct markup and CSS of each element.\n\n## Customizing components\n\nDefault components are provided for all standard features of the Portable Text spec, with logical HTML defaults. You can pass an object of components to use, both to override the defaults and to provide components for your custom content types.\n\nProvided components will be merged with the defaults. In other words, you only need to provide the things you want to override.\n\n```vue\n\u003cscript setup\u003e\nimport { PortableText } from '@portabletext/vue';\n\nconst myPortableTextComponents = {\n  types: {\n    image: ({ value }) =\u003e h('img', { src: value.imageUrl }),\n    callToAction: ({ value, isInline }, { slots }) =\u003e\n      isInline\n        ? h('a', { href: value.url }, value.text)\n        : h('div', { class: 'callToAction' }, value.text),\n  },\n\n  marks: {\n    link: ({ value }, { slots }) =\u003e {\n      const rel = !value.href.startsWith('/') ? 'noreferrer noopener' : undefined;\n      return h('a', { href: value.href, rel }, slots.default?.());\n    },\n  },\n};\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cPortableText :value=\"props.value\" :components=\"myPortableTextComponents\" /\u003e\n\u003c/template\u003e\n```\n\n## Single file components\n\nSingle file components can be used as custom renderers, and will receive the props listed below. In most components, the `value` prop will be the most frequently accessed. It is recommended that you define these props in your component, or you may want to [disable attribute inheritance](https://vuejs.org/guide/components/attrs.html#disabling-attribute-inheritance) if they are unused.\n\n| Prop       | Description                                            |\n| ---------- | ------------------------------------------------------ |\n| value      | Data associated with the node, e.g. the raw JSON value |\n| index      | Index with respect to the block’s parent               |\n| isInline   | Whether or not the node is “inline”                    |\n| renderNode | Internal rendering function, rarely needed             |\n\n### Defining props with TypeScript\n\nWhen using TypeScript, you can use `defineProps` with the `PortableTextComponentProps` type to prevent boilerplate prop definitions. Pass the structure of the `value` prop as a generic type parameter.\n\nExample of a single file component to render some custom buttons with an index and button text from the following PortableText:\n\n```json\n[\n  {\n    \"_type\": \"button\",\n    \"_key\": \"abc\",\n    \"text\": \"My Button\"\n  },\n  {\n    \"_type\": \"button\",\n    \"_key\": \"def\",\n    \"text\": \"Your Button\"\n  }\n]\n```\n\n```vue\n\u003cscript lang=\"ts\" setup\u003e\nimport type { PortableTextComponentProps } from '@portabletext/vue';\n\ndefineProps\u003c\n  PortableTextComponentProps\u003c{\n    text: string;\n  }\u003e\n\u003e();\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cbutton\u003e{{ index + 1 }}: {{ value.text }}\u003c/button\u003e\n\u003c/template\u003e\n```\n\n## Available components\n\nThese are the overridable/implementable keys:\n\n### `types`\n\nAn object of Vue components that renders different types of objects that might appear both as part of the input array, or as inline objects within text blocks - eg alongside text spans.\n\nUse the `isInline` property to check whether or not this is an inline object or a block.\n\nThe object has the shape `{typeName: VueComponent}`, where `typeName` is the value set in individual `_type` attributes.\n\nExample of rendering a custom `image` object:\n\n```vue\n\u003cscript setup\u003e\nimport { PortableText } from '@portabletext/vue';\nimport urlBuilder from '@sanity/image-url';\nimport { getImageDimensions } from '@sanity/asset-utils';\n\n// Barebones lazy-loaded image component\nconst SampleImageComponent = ({ value, isInline }) =\u003e {\n  const { width, height } = getImageDimensions(value);\n  return h('img', {\n    src: urlBuilder()\n      .image(value)\n      .width(isInline ? 100 : 800)\n      .fit('max')\n      .auto('format')\n      .url(),\n    alt: value.alt || ' ',\n    loading: 'lazy',\n    style: {\n      // Display alongside text if image appears inside a block text span\n      display: isInline ? 'inline-block' : 'block',\n      // Avoid jumping around with aspect-ratio CSS property\n      aspectRatio: width / height,\n    },\n  });\n};\n\nconst components = {\n  types: {\n    image: SampleImageComponent,\n    // Any other custom types you have in your content\n    // Examples: mapLocation, contactForm, code, featuredProjects, latestNews, etc.\n  },\n};\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cPortableText :value=\"somePortableTextInput\" :components=\"components\" /\u003e\n\u003c/template\u003e\n```\n\n### `marks`\n\nObject of Vue components that renders different types of marks that might appear in spans. Marks can be either be simple \"decorators\" (eg emphasis, underline, italic) or full \"annotations\" which include associated data (eg links, references, descriptions).\n\nIf the mark is a decorator, the component will receive a `markType` prop which has the name of the decorator (eg `em`). If the mark is an annotation, it will receive both a `markType` with the associated `_type` property (eg `link`), and a `value` property with an object holding the data for this mark.\n\nThe component also receives any children in the default slot that should (usually) be returned in whatever parent container component makes sense for this mark (eg `\u003ca\u003e`, `\u003cem\u003e`).\n\n```ts\n// `components` object you'll pass to PortableText w/ optional TS definition\nimport { PortableTextComponents } from '@portabletext/vue';\n\nconst components: PortableTextComponents = {\n  marks: {\n    // Ex. 1: custom renderer for the em / italics decorator\n    em: (_, { slots }) =\u003e h('em', { class: 'text-gray-600 font-semibold' }, slots.default?.()),\n\n    // Ex. 2: rendering a custom `link` annotation\n    link: ({ value }, { slots }) =\u003e {\n      const target = (value?.href || '').startsWith('http') ? '_blank' : undefined;\n      return;\n      h(\n        'a',\n        { href: value?.href, target, rel: target === '_blank' \u0026\u0026 'noindex nofollow' },\n        slots.default?.(),\n      );\n    },\n  },\n};\n```\n\n### `block`\n\nAn object of Vue components that renders portable text blocks with different `style` properties. The object has the shape `{styleName: VueComponent}`, where `styleName` is the value set in individual `style` attributes on blocks (`normal` being the default).\n\n```ts\n// `components` object you'll pass to PortableText\nconst components = {\n  block: {\n    // Ex. 1: customizing common block types\n    h1: (_, { slots }) =\u003e h('h1', { class: 'text-2xl' }, slots.default?.()),\n    blockquote: (_, { slots }) =\u003e\n      h('blockquote', { class: 'border-l-purple-500' }, slots.default?.()),\n\n    // Ex. 2: rendering custom styles\n    customHeading: (_, { slots }) =\u003e\n      h('h2', { class: 'text-lg text-primary text-purple-700' }, slots.default?.()),\n  },\n};\n```\n\nThe `block` object can also be set to a single Vue component, which would handle block styles of _any_ type.\n\n### `list`\n\nObject of Vue components used to render lists of different types (`bullet` vs `number`, for instance, which by default is `\u003cul\u003e` and `\u003col\u003e`, respectively).\n\nNote that there is no actual \"list\" node type in the Portable Text specification, but a series of list item blocks with the same `level` and `listItem` properties will be grouped into a virtual one inside of this library.\n\n```ts\nconst components = {\n  list: {\n    // Ex. 1: customizing common list types\n    bullet: (_, { slots }) =\u003e h('ul', { class: 'mt-xl' }, slots.default?.()),\n    number: (_, { slots }) =\u003e h('ol', { class: 'mt-lg' }, slots.default?.()),\n\n    // Ex. 2: rendering custom lists\n    checkmarks: (_, { slots }) =\u003e h('ol', { class: 'm-auto text-lg' }, slots.default?.()),\n  },\n};\n```\n\nThe `list` property can also be set to a single Vue component, which would handle lists of _any_ type.\n\n### `listItem`\n\nObject of Vue components used to render different list item styles. The object has the shape `{listItemType: VueComponent}`, where `listItemType` is the value set in individual `listItem` attributes on blocks.\n\n```ts\nconst components = {\n  listItem: {\n    // Ex. 1: customizing common list types\n    bullet: (_, { slots }) =\u003e\n      h('li', { style: { listStyleType: 'disclosure-closed' } }, slots.default?.()),\n\n    // Ex. 2: rendering custom list items\n    checkmarks: (_, { slots }) =\u003e h('li', ['✅', slots.default?.()]),\n  },\n};\n```\n\nThe `listItem` property can also be set to a single Vue component, which would handle list items of _any_ type.\n\n### `hardBreak`\n\nComponent to use for rendering \"hard breaks\", eg `\\n` inside of text spans.\n\nWill by default render a `\u003cbr /\u003e`. Pass `false` to render as-is (`\\n`)\n\n### `unknownMark`\n\nVue component used when encountering a mark type there is no registered component for in the `components.marks` prop.\n\n### `unknownType`\n\nVue component used when encountering an object type there is no registered component for in the `components.types` prop.\n\n### `unknownBlockStyle`\n\nVue component used when encountering a block style there is no registered component for in the `components.block` prop. Only used if `components.block` is an object.\n\n### `unknownList`\n\nVue component used when encountering a list style there is no registered component for in the `components.list` prop. Only used if `components.list` is an object.\n\n### `unknownListItem`\n\nVue component used when encountering a list item style there is no registered component for in the `components.listItem` prop. Only used if `components.listItem` is an object.\n\n## Disabling warnings / handling unknown types\n\nWhen the library encounters a block, mark, list or list item with a type that is not known (eg it has no corresponding component in the `components` property), it will by default print a console warning.\n\nTo disable this behavior, you can either pass `false` to the `onMissingComponent` property, or give it a custom function you want to use to report the error. For instance:\n\n```vue\n\u003cscript setup\u003e\nimport { PortableText } from '@portabletext/vue';\n\nconst onMissingComponent = (message, options) =\u003e {\n  myErrorLogger.report(message, {\n    // eg `someUnknownType`\n    type: options.type,\n    // 'block' | 'mark' | 'blockStyle' | 'listStyle' | 'listItemStyle'\n    nodeType: options.nodeType,\n  });\n};\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cPortableText\n    :value=\"[\n      /* array of portable text blocks */\n    ]\"\n    :onMissingComponent=\"false\"\n  /\u003e\n  \u003c!-- or, pass the function: --\u003e\n  \u003cPortableText\n    :value=\"[\n      /* array of portable text blocks */\n    ]\"\n    :onMissingComponent=\"onMissingComponent\"\n  /\u003e\n\u003c/template\u003e\n```\n\n## Rendering Plain Text\n\nThis module also exports a function (`toPlainText()`) that will render one or more Portable Text blocks as plain text. This is helpful in cases where formatted text is not supported, or you need to process the raw text value.\n\nFor instance, to render a meta description for a page:\n\n```ts\nimport { toPlainText } from '@portabletext/vue';\n\n// Imported from @unhead/vue\nuseHead({\n  meta: [{ name: 'description', value: toPlainText(myPortableTextData) }],\n});\n```\n\nOr to generate element IDs for headers, in order for them to be linkable:\n\n```ts\nimport { PortableText, toPlainText, PortableTextComponents } from '@portabletext/vue';\nimport slugify from 'slugify';\n\nconst LinkableHeader = ({ value }, { slots }) =\u003e {\n  // `value` is the single Portable Text block of this header\n  const slug = slugify(toPlainText(value));\n  return h('h2', { id: slug }, slots.default?.());\n};\n\nconst components: PortableTextComponents = {\n  block: {\n    h2: LinkableHeader,\n  },\n};\n```\n\n## Typing Portable Text\n\nPortable Text data can be typed using the `@portabletext/types` package.\n\n### Basic usage\n\nUse `PortableTextBlock` without generics for loosely typed defaults.\n\n```ts\nimport { PortableTextBlock } from '@portabletext/types';\n\ninterface MySanityDocument {\n  portableTextField: (PortableTextBlock | SomeBlockType)[];\n}\n```\n\n### Narrow types, marks, inline-blocks and lists\n\n`PortableTextBlock` supports generics, and has the following signature:\n\n```ts\ninterface PortableTextBlock\u003c\n  M extends PortableTextMarkDefinition = PortableTextMarkDefinition,\n  C extends TypedObject = ArbitraryTypedObject | PortableTextSpan,\n  S extends string = PortableTextBlockStyle,\n  L extends string = PortableTextListItemType,\n\u003e {}\n```\n\nCreate your own, narrowed Portable text type:\n\n```ts\nimport {\n  PortableTextBlock,\n  PortableTextMarkDefinition,\n  PortableTextSpan,\n} from '@portabletext/types';\n\n// MARKS\ninterface FirstMark extends PortableTextMarkDefinition {\n  _type: 'firstMark';\n  // ...other fields\n}\n\ninterface SecondMark extends PortableTextMarkDefinition {\n  _type: 'secondMark';\n  // ...other fields\n}\n\ntype CustomMarks = FirstMark | SecondMark;\n\n// INLINE BLOCKS\n\ninterface MyInlineBlock {\n  _type: 'myInlineBlock';\n  // ...other fields\n}\n\ntype InlineBlocks = PortableTextSpan | MyInlineBlock;\n\n// STYLES\n\ntype TextStyles = 'normal' | 'h1' | 'myCustomStyle';\n\n// LISTS\n\ntype ListStyles = 'bullet' | 'myCustomList';\n\n// CUSTOM PORTABLE TEXT BLOCK\n\n// Putting it all together by specifying generics\n// all of these are valid:\n// type CustomPortableTextBlock = PortableTextBlock\u003cCustomMarks\u003e\n// type CustomPortableTextBlock = PortableTextBlock\u003cCustomMarks, InlineBlocks\u003e\n// type CustomPortableTextBlock = PortableTextBlock\u003cCustomMarks, InlineBlocks, TextStyles\u003e\ntype CustomPortableTextBlock = PortableTextBlock\u003cCustomMarks, InlineBlocks, TextStyles, ListStyles\u003e;\n\n// Other BLOCKS that can appear inbetween text\n\ninterface MyCustomBlock {\n  _type: 'myCustomBlock';\n  // ...other fields\n}\n\n// TYPE FOR PORTABLE TEXT FIELD ITEMS\ntype PortableTextFieldType = CustomPortableTextBlock | MyCustomBlock;\n\n// Using it in your document type\ninterface MyDocumentType {\n  portableTextField: PortableTextFieldType[];\n}\n```\n\n## Credits\n\nThis repository is adapted from [@portabletext/react](https://github.com/portabletext/react-portabletext) which provided the vast majority of node rendering logic.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fportabletext%2Fvue-portabletext","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fportabletext%2Fvue-portabletext","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fportabletext%2Fvue-portabletext/lists"}