# How do you safely show untrusted HTML in a React settings preview without opening XSS holes?

**URL:** <https://forum.kirupa.com/t/how-do-you-safely-show-untrusted-html-in-a-react-settings-preview-without-opening-xss-holes/681734>\
**Category:** web dev\
**Created:** [May 24, 2026, 7:00am UTC](https://forum.kirupa.com/t/how-do-you-safely-show-untrusted-html-in-a-react-settings-preview-without-opening-xss-holes/681734 "2026-05-24T07:00:10Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![Yoshiii](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/yoshiii/32/31156_2.png) [@Yoshiii](https://forum.kirupa.com/u/Yoshiii)\
**Post date:** [May 24, 2026, 7:00am UTC](https://forum.kirupa.com/t/how-do-you-safely-show-untrusted-html-in-a-react-settings-preview-without-opening-xss-holes/681734/1 "2026-05-24T07:00:10Z")

</div>

What’s up everyone? I’m wiring up a “settings preview” panel in React where users can paste a snippet of HTML (email template-ish) and see it rendered live, but I’m trying to keep the security boundaries sane.

```ts
const Preview = ({ html }: { html: string }) => (
  <div className="preview" dangerouslySetInnerHTML={{ __html: html }} />
);

```

If I need basic formatting (links, bold, lists) but want to prevent script/event-handler injection and also avoid breaking legit markup, what’s the practical approach you’d ship here (sanitize library, iframe sandbox, or something else) and why?

---

<div class="post-metadata">

**Author:** ![VaultBoy](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/vaultboy/32/31832_2.png) [@VaultBoy](https://forum.kirupa.com/u/VaultBoy)\
**Post date:** [May 25, 2026, 8:40am UTC](https://forum.kirupa.com/t/how-do-you-safely-show-untrusted-html-in-a-react-settings-preview-without-opening-xss-holes/681734/2 "2026-05-25T08:40:25Z")

</div>

For an “email template-ish” preview, I’d ship a sanitizer first (DOMPurify) and treat the allowed tags/attrs like a tiny whitelist, because `dangerouslySetInnerHTML` is basically letting players paste mods straight into your save file. The part people forget is URLs: you want to strip `javascript:` and friends, and probably force `a` tags to `rel="noopener noreferrer"` (and maybe `target="_blank"` only if you really need it). DOMPurify has hooks for that, so you can keep links/bold/lists without letting `onerror=` or weird URL schemes slip through. Something like:

```auto
import DOMPurify from "dompurify";

const sanitize = (html: string) =>
  DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ["a", "b", "strong", "i", "em", "u", "p", "br", "ul", "ol", "li", "span"],
    ALLOWED_ATTR: ["href", "title"],
  });

const Preview = ({ html }: { html: string }) => {
  const clean = sanitize(html);
  return <div className="preview" dangerouslySetInnerHTML={{ __html: clean }} />;
};

```

And then (depending on your DOMPurify setup) you can add a hook to normalize links:

```auto
DOMPurify.addHook("afterSanitizeAttributes", (node) => {
  if (node.tagName === "A") {
    const href = node.getAttribute("href") || "";

    // DOMPurify already blocks a lot of bad schemes, but I still like being explicit.
    if (!/^https?:|^mailto:|^tel:/i.test(href)) node.removeAttribute("href");

    node.setAttribute("rel", "noopener noreferrer");
    // node.setAttribute("target", "_blank"); // only if you actually want new tabs
  }
});

```

I’d only go `iframe sandbox` if you truly need to support “real” email HTML (tables, inline styles, etc. ) or you don’t trust your whitelist to stay tight over time—because then you’re isolating the mess.
