Portable Text Lists Rendering as P Tags Instead of UL/LI Elements

23 replies
Last updated: Apr 20, 2021
Afternoon! I am struggling to parse lists via portable text. Im using the react-portable-text package and seem to be able to add custom classNames to h2's without any issues. However all of the bulleted list items in my unordered list are rendering to the dom as p tags and I cant seem to get the serializer to impact them. Any tips are appreciated! https://jmp.sh/S4NZ2qn
AI Update

Based on the thread you've linked and the context around your issue, the key problem is that you need to query the _raw version of your Portable Text field in GraphQL for lists to render properly. This is true for both react-portable-text and the official packages.

The Solution: Use _raw in Your GraphQL Query

When querying Portable Text through Sanity's GraphQL API, you need to use the _raw field (e.g., _rawBody instead of just body). The non-raw version doesn't include the proper structure for list items, which is why they're rendering as <p> tags.

Change your query from:

{
  body
}

To:

{
  _rawBody
}

Then pass _rawBody to your Portable Text component. This was confirmed as the solution by someone who had the exact same issue you're experiencing.

About react-portable-text

The react-portable-text package is a third-party library, not officially maintained by Sanity. While it can work once you use the _raw field, I'd recommend switching to the official @portabletext/react package for better long-term support and more predictable behavior with lists and other complex structures.

Better Long-Term Solution: Switch to @portabletext/react

The official package handles lists properly and has cleaner customization options:

npm install @portabletext/react

Usage example:

import { PortableText } from '@portabletext/react'

const components = {
  block: {
    h2: ({children}) => <h2 className="your-h2-class">{children}</h2>
  },
  list: {
    bullet: ({children}) => <ul className="your-list-class">{children}</ul>,
    number: ({children}) => <ol className="your-ordered-list-class">{children}</ol>
  },
  listItem: {
    bullet: ({children}) => <li>{children}</li>
  }
}

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

Note that it uses value (not content or blocks) and components (not serializers). You can find more details in the official Portable Text React documentation and the ultimate customization guide.

The bottom line: Query _rawBody (or _raw + your field name) in GraphQL, and your lists should render correctly regardless of which package you're using!

Show original thread
23 replies
Can’t say for sure but one thing comes to mind — you don’t have any serialisers for
ul
or
ol
so you effectively only serialising list items
Thanks, I was just playing with the idea of add a UL serializer. The odd thing is that they arent being picked up because each list item is being output as a P tag and there is no actual UL being output (even without any serializers).
What if you set your
li
serialiser to something crazy?
h1
will it use the h1 then?
No, it doesnt seem to change anything
Does your list actually have a value of
li
in Sanity? Usually you’ll serialize
bullet
or
number
. https://www.sanity.io/docs/block-type#lists-59b2751e6a05
I had a problem once with pasting a list from google docs…bullets were there but it wasn’t actually a list. Can this be a problem?
You might be on to something... I used the built in little bullet option to create the list and this is what is being output in the inspect: https://jmp.sh/fWfg4iK
I was basically just following the docs here to change the li's: https://www.sanity.io/plugins/react-portable-text
user A
Does that mean that you generally would select the items in the serializer with "bullet"
Yes, something like:

const serializers = {
  lists: {
    bullet: ({ children }) => <li style={{ color: 'red' }}>{children}</li>
  }
}
This indeed peculiar. The types of “list items” are blocks…
But the docs clearly refer to
ul
and
li
. As well as the code itself all the way down to the internal hyperscript package
got it…
react-portable-text
is a third party plugin
and the official one has
list
and
listItem
serialisers 🤷
Interesting, I also saw a note that you will want to use the "raw" selectors in the graphql query. Will try this now combined with that serializer to see if it helps.
I went through the code and it doesn’t make sense to me that list items are
_type: "block"
The official library expects it to be
_type: "list"
But I might be missing something. I’d trust
user A
more than me to be honest:)
Thanks for the help! I had tried the official plugin but switched to react-portable-text after quite a bit of fighting with it. Turns out that in both cases (block-content-to-react and react-portable-text) you needed to query the _raw version of the portable text in graphql for it to renders lists instead of paragraphs. This ultimately was the key for this to work: https://www.sanity.io/plugins/react-portable-text
Great to hear you got this working, Chad! 🙌

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?