Bug report. Though fixing this might change the behavior of this lib too much for users, so the solutions are probably to write documentation and/or provide an opt-in fix.
Yes.
Yes.
Yes, to the best of my abilities. :)
Both platforms.
Sorry, have not tried yet in a production build. I expect the same result though.
Have not tried, but the issue has a really simple setup so it shouldn’t differ.
Environment:
Target Platform:
Render this JSX:
<HTML html={' <div> foo\n\nbar baz </div> <div>zzz</div> '} />
I expected react-native-render-html to handle whitespace collapsing similarly to what HTML does. Replacing a rendered space character (U+0020 SPACE) with •, that would be:
foo•bar•baz
zzz
react-native-render-html (3.9.0 and master) renders:
••foobar••baz
zzz
What seems to work:
What seems broken:
I suspect this lib is limited by React Native’s Text component and errs on the side of not manipulating text too much, only removing newlines?
If that is the case, I can think of two possible improvements:
I’m going to implement some fixes on our side using simple regexps on our HTML. I can post what I come up with here if that’s useful. Or maybe I should do it in alterData for more fine-grained control?
Hi, thanks for the thorough report. I didn't notice this. I'd love it if you could take a stab at it and tell us what you come up with.
The goal of this library is to be as close as possible to what a webview would render, so if we can come up with a solution that somewhat improves this matter it would be great.
Depending on how much bloat this would add to the code, we can decide if we add it as a default behavior, or if we simply document this issue and provide an example of how to fix it.
This is the result I’m getting for a HTML string with lots of whitespace, first with no special processing and then with a custom alterData function that tries to mimic the HTML algorithm for whitespace collapsing:

The result is not as complete as what HTML does, for instance in the case of <p><a>foo </a> , bar</p>, web browsers will remove the space before the comma, but I’m not sure what the algorithm is and I’m not going that far.
The render part for this test:
const testContent = `
a
e
<div> foo\n\nbar baz </div> <div>zzz</div>
<div>
<div>
<div>
<blockquote>
Salut<a href="#"> les copains </a> ,
<span>comment ça va ?</span>
</blockquote>
</div>
</div>
</div>
`
return (
<View>
<View style={borderStyle}>
<HTML html={testContent} />
</View>
<View style={borderStyle}>
<HTML html={testContent} alterData={collapseHtmlText} />
</View>
</View>
)
Where collapseHtmlText is a separate module (around 90 lines). I’m still working on it so I’ll test with more content before sharing.
@Exilz The current whitespace handling has bugs too:
return <HTML html="<p>This <span>is</span> <strong>buggy</strong></p>" />
Shows:
This•isbuggy
You probably have a rule that a whitespace-only text node between two tag siblings can be dropped, but if the tags are both inline it should be kept (and collapsed to a single space if needed).
If you can point me to the right source files, I can have a look and maybe do a PR. :)
These parts in HTML.js are probably wrong:
if (type === 'text') {
if (!strippedData || !strippedData.length) {
// This is blank, don't render an useless additional component
return false;
}
// Text without tags, these can be mapped to the Text wrapper
return {
wrapper: 'Text',
data: data.replace(/(\r\n|\n|\r)/gm, ''), // remove linebreaks
attribs: attribs || {},
parent,
parentTag: parent && parent.name,
tagName: name || 'rawtext'
};
}
Use case:
<!-- We should render a single space between the links -->
<p>
<a href="…">foo</foo>
<a href="…">bar</foo>
</p>
Use case:
<!-- We should render a single space between the words -->
<p>foo
bar</p>
My current solution uses this module:
https://gist.github.com/fvsch/d66282c34d48c86ec90bf5acbbff03f4
Which I’m using in alterData in this way:
<HTML html={content} alterData={node => {
const newData = collapseText(node)
// return undefined to skip changing node text
return newData !== node.data ? newData : undefined
}} />
Of course it cannot fix the two issues highlighted in the previous comment, but it does handle the leading whitespace and uncollapsed whitespace issues.
I have seen that the space is removed not only for strong tag but even for em, s and u. For example: <p><strong>A</strong> <em>B</em> <u>C</u> <s>D</s></p>
It prints ABCD without spaces :(
@Draccan yup, these tags use the same logic as the ones fvsch pointed out.
@fvsch your solution in your gist looks pretty clean. It feels like a solid improvement, even if it's obviously not matching an actual browser rendering. I'll try it out more, and figure out whether if this can be added in the codebase by default.
I don't feel like documenting your gist and asking people to copy and paste a hundred lines into their own project just to add this feature, and I don't want to keep bloating the module with additional props either.
I'll measure the performances impact and the potential regressions (all help is welcome here !) and if everything is going smoothly, let's add it to the project. What do you think ?
Sounds like a good plan. And if there a risk it can change rendering significantly for library users, it could be a semver-major change anyway.
Is a big problem the space between the tag :(
i tried the @fvsch solution but doesn't work...
i have all collapse text because arrive from server with tag
<strong>Abbiamo</strong> <em>completato</em> <span style="text-decoration: underline;">l'installazione</span>
I think that now the best solution is to understand what kind of HTML you receive.
We are lucky because in the app we are developing the admin panel gives the possibility to enter text with bold, italic, paragraphs, etc.
So the HTML editor is an our component and we know how it puts HTML tags. If you're lucky like us you can use something like:
`
code:
addSpaceToHtml(htmlInput){
if(htmlInput !== null && htmlInput !== undefined){
let html = htmlInput;
html = html.replace(/<[/]strong> /g, " </strong>");
html = html.replace(/<[/]em> /g, " </em>");
html = html.replace(/<[/]i> /g, " </i>");
html = html.replace(/<[/]s> /g, " </s>");
html = html.replace(/<[/]u> /g, " </u>");
html = html.replace(/<[/]span> /g, " </span>");
return html;
}
}
`
Our editor puts spaces after tag closure and we put them before the tag closure in our app.
If you're taking html pages from web I don't recommend to use this approach and especially avoid regex!
@Draccan this is a good idea!
but work stange....
if use a constat work fine
`const htmlContent = 'Abbiamo completato l'installazione di tutto il pavimento in autobloccanti della 1a. Tappa della Smart City. Sono stati installati 188.000 m² di pavimento di alta qualità , la cui produzione proviene da SG Premoldados, installata nel nostro Polo Impresarial';
`if i use the dynamic content with the same structure your function doesn't work :( so strange
`
//typeTag={'Text'}
//numberOfLines={2}
style={newsListStyle.descNews}
/>`
@robertobrogi Al momento noi abbiamo un endpoint Firebase che ci restituisce un oggetto. Tra le proprietĂ di questo oggetto abbiamo "description" la quale contiene HTML. Non faccio altro che passargli il contenuto di quella proprietĂ .
Potresti provare a chiamare addSpaceToHtml in componentWillReceiveProps, nel costruttore o al mount e non direttamente sul componente HTML? Se non funziona così non so bene come aiutarti 🍡
@Draccan non funziona... sembra una cosa così banale... non capisco cosa non sta funzionando
indagherò
@robertobrogi Magari fatti stampare l'output in console della stringa HTML risultante, è possibile che avvenga qualcosa di particolare (i tag sono tutti aperti e chiusi correttamente? c'è qualche errore nella stringa html?).
Se vuoi invia l'errore così magari provo a dare un'occhiata. Sarebbe utile anche la sorgente HTML che stai usando se possibile.
We will publish the solution, if we find it, in english
i make a call api and i have a news map:
RENDER: before Your function: after after your function: maybe is the wrong regex?
` {
map(this.state.notices, (notice, index) => {
return (
<HTML html={this.addSpaceToHtml(notice.content)}
//typeTag={'Text'}
//numberOfLines={2}
style={newsListStyle.descNews}
/>
);
})
}
</View>
</ScrollView>`
<strong>Abbiamo</strong> <em>completato</em> <span style="text-decoration: underline;">l'installazione</span> di tutto il pavimento in autobloccanti della 1a. Tappa della Smart City. Sono stati installati 188.000 m² di pavimento di alta qualità , la cui produzione proviene da SG Premoldados, installata nel nostro Polo Impresarial
<strong>Abbiamo</strong> <em>completato</em> <span style="text-decoration: underline;">l'installazione</span> di tutto il pavimento in autobloccanti della 1a. Tappa della Smart City. Sono stati installati 188.000 m² di pavimento di alta qualità , la cui produzione proviene da SG Premoldados, installata nel nostro Polo Impresarial
It's not working in your example because you should have a space after "Abbiamo":
"Abbiamo[space]"
It's strange because the addSpaceToHtml function uses the replace method of strings.
Have u correctly copied the function?
html = html.replace(/<[/]strong> **SPACE**/g, " **SPACE**</strong>");
Pay attention to spaces. We are replacing all (/g) closure tags with one space after with one closure tag with a space before.
However, I use it in the app with an even more complex HTML full of things:

life is strange .... :)
now works!
the problem was thew regex:
is:
html = html.replace(/<[/]strong>/g, " ");
and NOT
html = html.replace(/<[/]strong> /g, " ");
without **space/g work fine...
Ok!
I've used the space after the tag closure because of this scenario: if I have
<strong>hello</strong><em>world</em>
I want to show hello_world_
But if I have
<strong>hello</strong> <em>world</em>
I want to show hello _world_
So I don't want to have a space between strings if the 2 tags are attached.
The one you've written (html = html.replace(/<[/]strong>/g, " ");) will remove the strong tag closure and replace it with a space.
For the problem where react-native-render-html suppresses newline characters (instead of rendering them as a space), did you try replacing your newline characters first?
<HTML html={myHtmlContent.replace(/\r?\n/g, ' ')} />
I noticed that the whitespace was being rendered unlike with HTML so I took a crack at filtering the content with an alterData like so:
alterData (node) {
let { parent, data } = node
if (parent && ['li','p'].includes(parent.name))
{
return data.trim()
}
}
Seems to be much better now. Though it might not be foolproof/bug-free.
Anyone having issues with spaces between adjacent links? I've tried all spacing methods and all are being ignored:

@djpetenice same problem here.
If the content is (with space between a):
<p><a href="abc">abc</a> <a href="def">def</a></p>
It renders (without space):
I did a hack by adding a span with a single character and styling it the same colour as my background.
Based on @djpetenice genius idea, I created this function:
export const fixHtmlBlockSpaces = (str = '') => {
const fixedStr = str.replace(/(<\/[^>]+>)\s+(<)/gm, (substring, group1, group2) => {
return `${group1}<span style="color: transparent">_</span>${group2}`;
});
return fixedStr;
};
Now this:
<p><a href="abc">abc</a> <a href="def">def</a></p>
Is replaced by this:
<p><a href="abc">abc</a><span style="color: transparent">_</span><a href="def">def</a></p>
@douglasjunior Your example works for me. But one problem its takes too space than normal space. Need more better solution from library.
I also getting error on emoji image. I put below content:
hjghjg <img class="emojioneemoji" src="1f603.png"> dfdfdfdf <img title=":nerd:" class="emojioneemoji" src="1f913.png"> dfdf df df <img title=":upside_down:" class="emojioneemoji" src="1f643.png">dfdf <img title=":upside_down:" class="emojioneemoji" src="1f643.png"><img class="emojioneemoji" src="1f609.png"> dfdf dfdf<br>df kjhjkh jh<br>djhf jkjlk<br>djhfj<br>dkhjf h<br>jjj <img class="emojioneemoji" src="1f606.png"><img class="emojioneemoji" src="1f606.png"> dfdf dfdf<br>lkkkk <img class="emojioneemoji" src="1f606.png">
Expected Behaviour :
hjghjg :smiley: dfdfdfdf :nerd_face: dfdf df df :upside_down_face: dfdf :upside_down_face: :wink: dfdf dfdf
df kjhjkh jh
djhf jkjlk
djhfj
dkhjf h
jjj :laughing: :laughing: dfdf dfdf
lkkkk :laughing:
Actual Behaviour :

Plz suggest any solution.
@Draccan yup, these tags use the same logic as the ones fvsch pointed out.
@fvsch your solution in your gist looks pretty clean. It feels like a solid improvement, even if it's obviously not matching an actual browser rendering. I'll try it out more, and figure out whether if this can be added in the codebase by default.
I don't feel like documenting your gist and asking people to copy and paste a hundred lines into their own project just to add this feature, and I don't want to keep bloating the module with additional props either.I'll measure the performances impact and the potential regressions (all help is welcome here !) and if everything is going smoothly, let's add it to the project. What do you think ?
Any update on this happening?
Any update on this happening?
Finally I just switched to react-native-htmlview.
thanks, anyway.
I made a function to fix the white space between tags.
function fixSpaceInHTML(html){
let tagWithSpaceMatch = /<([^>]+)>\s+<([^>]+)>/.exec(html);
if(tagWithSpaceMatch){
let tagWithSpace = tagWithSpaceMatch[0];
let newTagWithSpace = tagWithSpace.replace(/\s/g, '') + " ";
let newHtml = html.replace(tagWithSpace, newTagWithSpace);
console.log({newHtml});
return fixSpaceInHTML(newHtml);
}else{
console.log({html});
return html;
}
}
That is working fine.
I hope it is helpful for anyone.
@lovecoding530 Is it working for image issue?? I need a solution for image rendering.
Not sure.
that is working for white space issue
Ooo, ok. That was solved by the previous solution by @douglasjunior. Any solution for image issue?
@Exilz is there any solution for image render problem?
I did a hack by adding a span with a single character and styling it the same colour as my background.
Jajajajajaja thinking so seriously to do the same and add a style in web to make it look similar.
Are there any plans to resolve this issue any time soon? Or are workarounds the recommended solution at the moment?
Thanks
One possible workaround I've found, although I can't vouch for it as I haven't tested it fully, is tricking the library into thinking that the text node is not fully whitespace. This seems to prevent the node from being collapsed. Here I add a zero-width character to whitespace data:
alterData: node => {
if (node.data.match(/\s+/)) {
return `${data}\u200C`
}
}
In the cases I've seen, this seems to stop the whitespace from being lost. However I'm not sure if there's other impact.
Applying the following replace function to data before passing it to the component works for all our content so far:
.replace(/[\t\r\n ]+/g, ' ')
I've done some research to see where those collapsing rules are specified. It appears to be the CSS rule white-space. The complete reference algorithm is defined in the CSS Text Module Level 3, sections 3 and 4.
Full Reference
4.1.1. Phase I: Collapsing and Transformation
For each inline (including anonymous inlines; see [CSS2] section 9.2.2.1) within an inline formatting context, white space characters are processed as follows prior to line breaking and bidi reordering, ignoring bidi formatting characters (characters with the
Bidi_Controlproperty
If white-space is set to normal, nowrap, or pre-line, white space characters are considered collapsible and are processed by performing the following steps:
- Any sequence of collapsible spaces and tabs immediately preceding or following a segment break is removed.
- Collapsible segment breaks are transformed for rendering according to the segment break transformation rules.
- Every collapsible tab is converted to a collapsible space (U+0020).
- Any collapsible space immediately following another collapsible space—even one outside the boundary of the inline containing that space, provided both spaces are within the same inline formatting context—is collapsed to have zero advance width. (It is invisible, but retains its soft wrap opportunity, if any.)
If white-space is set to pre, pre-wrap, or break-spaces, any sequence of spaces is treated as a sequence of non-breaking spaces. However, for pre-wrap, a soft wrap opportunity exists at the end of a sequence of spaces and/or tabs, while for break-spaces, a soft wrap opportunity exists after every space and every tab.
4.1.2. Phase II: Trimming and Positioning
Then, the entire block is rendered. Inlines are laid out, taking bidi reordering into account, and wrapping as specified by the white-space property. As each line is laid out,
- A sequence of collapsible spaces at the beginning of a line is removed.
- If the tab size is zero, preserved tabs are not rendered. Otherwise, each preserved tab is rendered as a horizontal shift that lines up the start edge of the next glyph with the next tab stop. If this distance is less than 0.5ch, then the subsequent tab stop is used instead. Tab stops occur at points that are multiples of the tab size from the block’s starting content edge. The tab size is given by the tab-size property.
Note: See [UAX9] for rules on how U+0009 tabulation interacts with bidi.
- A sequence at the end of a line of collapsible spaces is removed, and any trailing U+1680 OGHAM SPACE MARK is also removed if it’s white-space property is normal, nowrap, or pre-line.
In the case of bidirectional text, any sequence of collapsible spaces located at the end of the line prior to bidi reordering [CSS-WRITING-MODES-3] is also removed, and bidi reordering is applied on the remaining content of the line.
- If there remains any sequence of white space, and/or other space separators, at the end of a line (after bidi reordering [CSS-WRITING-MODES-3]):
- If white-space is set to normal, nowrap, or pre-line, the UA must hang this sequence (unconditionally).
- If white-space is set to pre-wrap, the UA must (unconditionally) hang this sequence, unless the sequence is followed by a forced line break, in which case it must conditionally hang the sequence is instead. It may also visually collapse the character advance widths of any that would otherwise overflow.
Note: Hanging the white space rather than collapsing it allows users to see the space when selecting or editing text.
- If white-space is set to break-spaces, hanging or collapsing the advance width of the spaces, tabs, or other space separators at the end of the line is not allowed; those that overflow must wrap to the next line.
Glossary
| inline | An inline is an element inside of an inline formatting context. |
| inline formatting context | https://www.w3.org/TR/CSS2/visuren.html#inline-formatting |
| segment break | https://www.w3.org/TR/2020/WD-css-text-3-20200429/#segment-break |
| inter-element whitespace | https://html.spec.whatwg.org/multipage//dom.html#inter-element-whitespace |
But this reference considers multiple contexts that we can ignore in a minimal compatibility approach. The reference describes the required behavior for multiple values of the white-space CSS property, normal, pre-line, nowrap... We can keep focus on the normal value, since this is the default behavior reported by @fvsch. Also, bidirectional layouts for RTL can be considered later, because they add complexity and are limited by React Native own support of these features. Moreover, there seems to be other kind of subtleties depending on localization. Here are the highlights of the spec I have identified. :
white-space: normal;:The W3C consortium also provides a gigantic test suite, and one folder is specifically dedicated to CSS whitespaces which can be a source for inspiration. In the meantime, I have started to implement some basic tests regarding whitespaces, see 53b8679ddc74badb486348a7404fc835527cb7f4 and d76f99d9d44b3bb38fe92a7403b1165e6b10e765. A majority of them fail, of course, which is the point of this issue!

I've just written a RFC to fix this issue in an upcoming version. You can find it here!
My current solution uses this module:
https://gist.github.com/fvsch/d66282c34d48c86ec90bf5acbbff03f4Which I’m using in alterData in this way:
<HTML html={content} alterData={node => { const newData = collapseText(node) // return undefined to skip changing node text return newData !== node.data ? newData : undefined }} />Of course it cannot fix the two issues highlighted in the previous comment, but it does handle the leading whitespace and uncollapsed whitespace issues.
@fvsch what was the module that you use?
I am currently developing this behavior as part of a service for Expensify. The new engine following the whitespace RFC is being implemented here: https://github.com/native-html/core. An early release should be available in the upcoming week.
This pre-release will be part of the 6.x release cycle. If you are wondering why we're jumping from 4 to 6, the reason is that 6.x will require more recent versions of React Native, and we want all users to benefit from the 5.x enhancements already available in alpha. Also, the new engine changes the structure of nodes available with onParsed, and the renderers prop will probably look different.
I've made great progress with the new release! Given this snippet:
<span>This is text!</span>
This is <strong>bold</strong> <em>italics</em>.
We now have:
whiteSpace: pre; |
whiteSpace: normal; |
|---|---|
|  |  |
You will be able to control the whitespace behavior with the special whiteSpace style property in any of the places you could previously customize styles (baseFontStyles, tagsStyles ...etc).
| This issue has been fixed in the Foundry release. Try it out now! See #430 for instructions. |
|
Hi @jsamr
I could not apply "whiteSpace". I got this warning message:
`Failed prop type: Invalid props.style key 'whiteSpace' supplied to 'Text'
Could you please tell me how can I apply it? Thanks
@thuongtv-vn Are you sure you're using the foundry pre-release? Best to continue the discussion on our discord, the foundry thread (#430) or a separate issue, thank you !
solved this issue using following code:
const filterQuestion = (q, curr) => {
const tags = /(<\/?(?:img|br)[^>]*>)|<[^>]+>/gi;
// const linebreaks = /(\r\n|\n|\r)/g;
const tagSpaces = /\s/g;
const subst = `$1`;
let filteredQuestion = `<p>Q ${curr + 1}: ${q
.replace(tags, subst)
.replace(tagSpaces, ' ')}</p>`;
console.log(filteredQuestion);
return filteredQuestion;
};
might this would be helpful to eliminate the unwanted spaces and tags and just retain the desired tags you need in your html to render.
Jazak ALLAH
Most helpful comment
Based on @djpetenice genius idea, I created this function:
Now this:
Is replaced by this: