Adding custom bullet lists in Sanity's Portable Text editor

8 replies
Last updated: Feb 6, 2025
Hi There,
I'm trying to add a custom bullet list to my portableText editor, like:

lists: [
        {title: 'Bullet', value: 'bullet'},
        {title: 'Numbered', value: 'number'},
        {
          title: 'Checkmarks', 
          value: 'checkmarks', 
          icon: UlistIcon,
        }
      ],
This works nicely, but within the editor it just shows up as tabs.
Does anyone know how to add some styling to this so I can actually show checkmarks, or at least show bullets so that the one editing it is able to see thats it's a list?
AI Update

Great question! You're on the right track with adding custom list types to your Portable Text editor. The issue is that while you can define custom list types in your schema, there isn't currently a built-in way to customize how they appear visually in the Sanity Studio editor itself.

The Current Limitation

When you define custom list types in your Portable Text schema, Sanity recognizes them and stores them correctly in your content, but the visual representation in the Studio editor is quite limited. The editor uses a standard rendering for all list types, which is why you're seeing tabs rather than custom visual indicators like checkmarks or styled bullets.

What Works vs. What Doesn't

Unlike decorators (bold, italic, etc.) which support the blockEditor property with render and icon options for customizing their appearance in the editor, list types don't have this same capability. As shown in the decorators documentation, you can customize decorator rendering like this:

decorators: [
  {
    title: "Sup",
    value: "sup",
    blockEditor: {
      icon: () => <sup>sup</sup>,
      render: ({ children }) => <sup>{children}</sup>,
    },
  },
]

However, list types don't support a similar blockEditor or component property for custom rendering within the Studio editor.

Full Control on the Frontend

The good news is that your data is being stored correctly with value: 'checkmarks', and you have complete control over how these lists render on your frontend. Using @portabletext/react or similar rendering libraries, you can create custom components:

const components = {
  list: {
    checkmarks: ({children}) => (
      <ul className="checkmark-list">
        {children}
      </ul>
    )
  },
  listItem: {
    checkmarks: ({children}) => (
      <li className="checkmark-item">
        βœ“ {children}
      </li>
    )
  }
}

Workarounds for Better Editor Experience

To help your content editors understand which list type they're using:

  1. Use descriptive titles and icons: You're already doing this with title: 'Checkmarks' and icon: UlistIcon, which helps editors identify the list type in the formatting toolbar

  2. Add descriptions: Include a description field in your list configuration to explain what each list type does

  3. Create documentation: Provide internal documentation with screenshots showing how each list type will appear on the live site

This is a known limitation of the current Portable Text Editor in Studio. The editor focuses on content structure rather than exact visual fidelity, with the expectation that the final rendering happens on your frontend where you have complete styling control.

i added the following css
.pt-list-item-checkmarks [data-list-item="checkmarks"] [data-list-prefix] > span::before {
    content: "β– ";
    font-size: 1.0em;
    display: inline-block;
    padding-top: 2px;
    color: crimson;
}
I tried that, but not really sure where to put it /imoprt itπŸ˜“
or can i just import it in the sanity.config.ts?
in your schema you can import a seperate .css file
so textField.tsx

import './textField.css'


export const textField = defineType({.....
ah i can... i just had a wrong css selector i guess
o thats even better for keeping it organized
Love it, thanks
user Z
πŸ‘ No problem

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?