Custom decorator not rendering in BlockContent frontend (React + Sanity)

1 replies
Last updated: Jan 28, 2026
user G
Hi, I have built a blog using your tutorial: Build your first blog using React. After that, I have added a decorator for highlighting text. [Highlighted text will have a background color of yellow]. In the portable text editor, whenever I highlight any text, the background color becomes yellow, as expected. However, in the frontend, the highlighting is not applied. What do I need to do? I have been struggling with this for the last two days. Pls help me here. The code snippets are as follows:
src/components/OnePost.js

import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import sanityClient from "../client.js";
import BlockContent from "@sanity/block-content-to-react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { coldarkDark } from "react-syntax-highlighter/dist/esm/styles/prism";

export default function OnePost() {
  const [postData, setPostData] = useState(null);
  const { slug } = useParams();

  const serializers = {
    types: {
      code: (props) => (
        <SyntaxHighlighter
          language={props.node.language}
          style={coldarkDark}
          showLineNumbers
          lineNumberStyle={{
            padding: "0 5px 0 0",
            fontSize: 14,
            borderRight: "1.5px solid darkgray",
            marginRight: "10px",
          }}
        >
          {props.node.code}
        </SyntaxHighlighter>
      ),
    },
  };

  useEffect(() => {
    sanityClient
      .fetch(
        `*[slug.current == $slug]{
          title,
          slug,
          mainImage{
            asset->{
              _id,
              url
             }
           },
         body,
        "name": author->name,
        "authorImage": author->image
       }`,
        { slug }
      )
      .then((data) => setPostData(data[0]))
      .catch(console.error);
  }, [slug]);

  if (!postData) return <div>Loading...</div>;

  return (
    <div className="col-11 col-sm-10 col-md-6 mx-auto mt-5">
      <div>
        <h1 className="font-weight-bold">{postData.title}</h1>
        <div>
          <h6 className="text-secondary">{postData.name}</h6>
        </div>
      </div>
      <div className="text-muted">
        <BlockContent
          blocks={postData.body}
          projectId={sanityClient.projectId}
          dataset={sanityClient.dataset}
          serializers={serializers}
        />
      </div>
    </div>
  );
}
sanityblog/blockContent.js

import React from "react";

/**
 * This is the schema definition for the rich text fields used for
 * for this blog studio. When you import it in schemas.js it can be
 * reused in other parts of the studio with:
 *  {
 *    name: 'someName',
 *    title: 'Some title',
 *    type: 'blockContent'
 *  }
 */

const highlightRender = (props) => (
  <span style={{ backgroundColor: "yellow" }}>{props.children}</span>
);

export default {
  title: "Block Content",
  name: "blockContent",
  type: "array",
  of: [
    {
      title: "Block",
      type: "block",
      // Styles let you set what your user can mark up blocks with. These
      // correspond with HTML tags, but you can set any title or value
      // you want and decide how you want to deal with it where you want to
      // use your content.
      styles: [
        { title: "Normal", value: "normal" },
        { title: "H1", value: "h1" },
        { title: "H2", value: "h2" },
        { title: "H3", value: "h3" },
        { title: "H4", value: "h4" },
        { title: "Quote", value: "blockquote" },
      ],
      lists: [{ title: "Bullet", value: "bullet" }],
      // Marks let you mark up inline text in the block editor.
      marks: {
        // Decorators usually describe a single property – e.g. a typographic
        // preference or highlighting by editors.
        decorators: [
          { title: "Strong", value: "strong" },
          { title: "Emphasis", value: "em" },
          {
            title: "Highlight",
            value: "highlight",
            blockEditor: {
              icon: () => "H",
              render: highlightRender,
            },
          },
        ],
        // Annotations can be any object structure – e.g. a link or a footnote.
        annotations: [
          {
            title: "URL",
            name: "link",
            type: "object",
            fields: [
              {
                title: "URL",
                name: "href",
                type: "url",
              },
              {
                title: "Open in new tab",
                name: "blank",
                description: "Read <https://css-tricks.com/use-target_blank/>",
                type: "boolean",
              },
            ],
          },
        ],
      },
    },
    // You can add additional types here. Note that you can't use
    // primitive types such as 'string' and 'number' in the same array
    // as a block type.
    {
      type: "image",
      options: { hotspot: true },
    },

    {
      type: "code",
    },
  ],
};
AI Update

I can see your issue! You've correctly set up the decorator in your Sanity schema, but you're missing the corresponding serializer in your frontend code to render it.

In your OnePost.js, your serializers object only handles the code type, but it doesn't include anything for your custom highlight decorator. You need to add a marks section to tell @sanity/block-content-to-react how to render your highlighted text.

Here's the fix - update your serializers object:

const serializers = {
  types: {
    code: (props) => (
      <SyntaxHighlighter
        language={props.node.language}
        style={coldarkDark}
        showLineNumbers
        lineNumberStyle={{
          padding: "0 5px 0 0",
          fontSize: 14,
          borderRight: "1.5px solid darkgray",
          marginRight: "10px",
        }}
      >
        {props.node.code}
      </SyntaxHighlighter>
    ),
  },
  marks: {
    highlight: (props) => (
      <span style={{ backgroundColor: "yellow" }}>{props.children}</span>
    ),
  },
};

The key addition is the marks object with your highlight decorator. The decorator name (highlight) must match what you defined in your blockContent.js schema.

Quick note on the library you're using: @sanity/block-content-to-react is deprecated in favor of @portabletext/react, which is the current recommended library. For future projects, you should consider migrating. The main difference is that it uses a components prop instead of serializers, and props.node becomes props.value.

But for now, adding the marks section should solve your highlighting issue immediately!

Show original thread
1 reply

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?