Styling Portable Text blocks with @portabletext/react components

8 replies
Last updated: Feb 22, 2022
Hello again everyone! I am trying to style a portable text component with '@portabletext/react' and I can't make any sense of the documentation...
I have my components defined like so.

const ptComponents = {
    block: {
      h1: ({ value }: any) => {
        return <h1 className="whiteText">{value}</h1>;
      },
      p: ({ value }: any) => {
        return <p className="whiteText">{value}</p>;
      },
    },
  };
And I am passing the components to the Portable Text Component here.

<PortableText value={project.fulldesc} components={ptComponents} />
The styles are not being applied though. What am I doing wrong? I seriously can't make out how to do this:
https://github.com/portabletext/react-portabletext
AI Update

I can see your issue! The problem is that you're using value in your component props, but for block-level elements like headings and paragraphs, you should be using children instead. The value prop contains the raw block data, while children contains the already-rendered text content.

Here's the corrected code:

const ptComponents = {
  block: {
    h1: ({ children }) => {
      return <h1 className="whiteText">{children}</h1>;
    },
    p: ({ children }) => {
      return <p className="whiteText">{children}</p>;
    },
  },
};

The key difference: children instead of value.

Why this matters

When @portabletext/react processes block-level elements (like paragraphs and headings), it:

  1. Processes all the inline content (text, marks, annotations)
  2. Passes the rendered result as children to your custom component
  3. The value prop contains the raw JSON structure of the block, which isn't what you want to render

Additional tips

If you need access to the raw block data (for example, to check custom fields), you can use both:

h1: ({ children, value }) => {
  console.log(value); // Raw block data
  return <h1 className="whiteText">{children}</h1>;
}

You can also apply different styles based on the block's properties:

block: {
  normal: ({ children }) => <p className="whiteText">{children}</p>,
  h1: ({ children }) => <h1 className="whiteText text-4xl">{children}</h1>,
  h2: ({ children }) => <h2 className="whiteText text-3xl">{children}</h2>,
}

For more complex styling scenarios, check out the ultimate guide for customising Portable Text which covers everything from schema to React components.

Show original thread
8 replies
Hi
user R
, in your code you pass value (that is an array of portable text blocks) to block which returns an error -
Objects are not valid as a React child
. If you pass
props
instead - your code should work.
const ptComponents = {
    block: {
      h1: ({ props }: any) => {
        return <h1 className="whiteText">{props}</h1>;
      },
      p: ({ props }: any) => {
        return <p className="whiteText">{props}</p>;
      },
    },
  };
Should be
({ children })
instead of
({ value })
, this should work:
const ptComponents = {
  block: {
    h1: ({ children }: any) => {
      return <h1 className="whiteText">{children}</h1>;
    },
    p: ({ children }: any) => {
      return <p className="whiteText">{children}</p>;
    },
  },
};
Okay, I have tried passing both props and children and neither of these are working either.
user R
, could you paste your blockContent schema. Are there’re elements with the
p
and
h1
values? 🙂
 export default {
  title: 'Block Content',
  name: 'blockContent',
  type: 'array',
  of: [
    {
      title: 'Block',
      type: 'block',
      styles: [
        { title: 'H1', value: 'h1'},
        { title: 'P', value: 'p'},
      ],
    }
  ],
};
Thank you all. My issue was that 'p' was not defined in my blockContent schema. It was defined as 'normal'. Everything is working as expected now!
Racheal, just to answer your question, the text would come through perfectly sized, but wouldn't apply my css coloring rules.

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?