Handling `<strong>` spans in Portable Text without unwanted linebreaks

5 replies
Last updated: May 12, 2021
I’m trying to render portable Text using the serializer. My problem is how to deal with the spans that have &lt;strong&gt; decorators. If I render the normal spans in a div there will be a linebreak after every span, including strong span (meaning in the middle of a sentence). And if I render it in a span, all my paragraphs within the same block will render as one single paragraph.
AI Update

This is a common misunderstanding about how Portable Text's structure works! The key issue is that you shouldn't be rendering individual spans directly as separate elements - spans are meant to be rendered inline within their parent block.

Here's what's happening: In Portable Text, a block (like a paragraph) contains an array of children (spans), and those spans can have marks (like strong decorators). The proper way to render this is:

  1. The block (e.g., a paragraph) should be rendered as a block-level element (like <p> or <div>)
  2. The spans within that block should be rendered inline (without wrapping divs)
  3. Decorators like strong should wrap the text content of those specific spans

The problem you're experiencing happens when you try to render each span as its own component. Instead, you need to process the spans as part of the block rendering.

Correct Approach

When using the official serializers like @portabletext/react, this is handled automatically. The block serializer processes all children spans together, applying marks inline:

import {PortableText} from '@portabletext/react'

// The library handles this correctly by default
<PortableText value={portableTextContent} />

If Building a Custom Serializer

If you're building your own serializer, you need to:

  1. Render the block container (paragraph)
  2. Map through the children spans
  3. Apply marks to the text content inline, not as separate block elements
// Simplified example
const renderBlock = (block) => {
  return (
    <p>
      {block.children.map((span, index) => {
        let text = span.text;
        
        // Apply marks inline
        if (span.marks?.includes('strong')) {
          text = <strong key={index}>{text}</strong>;
        }
        if (span.marks?.includes('em')) {
          text = <em key={index}>{text}</em>;
        }
        
        return text;
      })}
    </p>
  );
};

The critical point: don't wrap each span in a div. Spans are inline content that should flow together within their parent block. Only the block itself should be a block-level element.

If you're using a library and still experiencing this issue, you might be customizing the serializers incorrectly. Check the Portable Text introduction guide for more details on the data structure, and make sure your custom serializers for marks/decorators return inline elements, not block-level ones.

Show original thread
5 replies
I'm using block-content-to-react. At the moment my serializer looks like this:

const serializers = {
 
types: {
  
block: (props: any) => (
   
props.children.map((child: any, index: React.Key) => {
    
switch (props.node.style) {
     
case 'h2':
      
return <MarginTopFour><MarginBottomOne><h2 key={index}>{child}</h2></MarginBottomOne></MarginTopFour>;
     
case 'h3':
      
return <MarginTopThree><MarginBottomOne><h3 key={index}>{child}</h3></MarginBottomOne></MarginTopThree>;
     
case 'normal':
      
return <span key={index}>{child}</span>;
     
default:
      
return <span key={index}>{child}</span>;
    
}
   
})
  
),
 
},
 
marks: {
    
strong: (props: any) => <DarkSpan>{props.children}</DarkSpan>,
 
},

};
This code puts all the different paragraphs together as one. If I change
case: 'normal'
to return a div instead of a span, the spans with strong decorator will also be on a individual line.
Does
<DarkSpan />
render inline? If it does, that part looks good.
I may be off track but I’m not sure that normal should return a span
or a div. I’d be curious what you get if you just return
{child}
. I’m also looking at this example and wondering what you get if you
return BlockContent.defaultSerializers.types.block({ child })
.
Yes, &lt;DarkSpan /&gt; render inline.
If I return {child} without div or span it work fine so do the code from codeSandbox example. But I want to put some styling on the rendered text. Can I combine it with styled components, that's what I'm trying to do? If yes, how do I do? If no, how can I style the text?
We've solved the problem. When we map through the children we affect every single span instead of the whole paragraph. So if we return {props.children} instead of {child} it works perfectly.
case 'normal':

return <p>{props.children}</p>;
Thanks
user A
for looking into this.

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?