Declaring Nested Lists the Block Type for PortableText

1 replies
Last updated: Jan 5, 2023
Hello, I have these nested lists,
ul
and
ol
, but I don't know how to declare the block type of these lists in order to use them in PortableText
AI Update

Looking at your question about declaring nested lists (ul and ol) in PortableText, I can help clarify how lists work in Sanity's block type schema.

The key thing to understand: Lists in PortableText are actually configured through the block type itself, not as separate types. By default, the block type in Sanity already supports lists - you don't need to declare them separately.

Here's how to configure lists in your schema:

defineField({
  name: 'content',
  type: 'array',
  of: [
    {
      type: 'block',
      // Configure which list types are available
      lists: [
        {title: 'Bullet', value: 'bullet'},
        {title: 'Numbered', value: 'number'}
      ],
      // Configure list item levels (for nesting)
      styles: [
        {title: 'Normal', value: 'normal'},
        {title: 'H1', value: 'h1'},
        // ... other styles
      ],
      marks: {
        // ... your marks configuration
      }
    }
  ]
})

Important points about rendering:

  1. Use the _raw field in queries: When querying PortableText content (especially with GraphQL), you need to query the _raw version of the field for lists to render properly. Without this, list items may appear as regular paragraphs. This is the most common issue people encounter.
query {
  post {
    _rawContent  // Use _raw prefix
  }
}
  1. Rendering with @portabletext/react: When customizing list rendering, you target the list type (bullet or number), not ul or li:
import {PortableText} from '@portabletext/react'

const components = {
  list: {
    bullet: ({children}) => <ul className="custom-bullet-list">{children}</ul>,
    number: ({children}) => <ol className="custom-numbered-list">{children}</ol>
  },
  listItem: {
    bullet: ({children}) => <li className="custom-bullet-item">{children}</li>,
    number: ({children}) => <li className="custom-number-item">{children}</li>
  }
}

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

For nested lists: Sanity's block editor supports nested lists out of the box. Users can indent list items in the editor using Tab/Shift+Tab, and the nesting structure is automatically preserved in the PortableText data.

The confusion often comes from the fact that lists in PortableText are stored as blocks with listItem and level properties rather than as nested HTML-like structures. The rendering libraries handle converting this flat structure into proper nested HTML automatically.

If you're still seeing issues with lists not rendering correctly, make sure you're:

  • Querying the _raw field (this is usually the culprit!)
  • Using a proper PortableText renderer like @portabletext/react
  • Not accidentally filtering out blocks with listItem properties in your query
I just saw I can use this:
 list: {
    // Ex. 1: customizing common list types
    bullet: ({children}) => <ul className="mt-xl">{children}</ul>,
    number: ({children}) => <ol className="mt-lg">{children}</ol>,

    // Ex. 2: rendering custom lists
    checkmarks: ({children}) => <ol className="m-auto text-lg">{children}</ol>,
  },

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?