Modern React applications usually provide strong protection against common Cross-Site Scripting (XSS) attacks because React escapes text inserted into the DOM by default.

For example:

function UserProfile({ name }) {
  return <h2>{name}</h2>;
}

If name contains HTML, React treats it as text rather than executing it as markup.

The security picture changes when an application intentionally works with raw HTML, browser DOM APIs, third-party libraries, or APIs such as dangerouslySetInnerHTML.

This is where Trusted Types can provide another layer of protection.

React 19.3 improves its integration with Trusted Types, allowing applications to use browser-enforced Trusted Types policies more effectively when React writes content to sensitive DOM sinks.

This article explains what Trusted Types are, how DOM injection happens, how React's integration works, and how developers can use the feature without weakening existing application security.

What Is DOM Injection?

DOM injection occurs when untrusted data reaches a browser API that interprets the data as HTML, JavaScript, or another executable format.

A simple example is:

element.innerHTML = userInput;

If userInput contains HTML that the application did not intend to execute, the browser may interpret it as markup.

For example, code such as:

const content = "<img src=x onerror=alert('XSS')>";
element.innerHTML = content;

creates an unsafe DOM injection scenario.

React normally avoids this problem when rendering ordinary strings:

function Message({ message }) {
  return <div>{message}</div>;
}

React escapes the value instead of treating it as HTML.

The risk increases when developers intentionally bypass normal escaping.

What Is Trusted Types?

Trusted Types is a browser security mechanism that helps prevent dangerous strings from being passed directly into sensitive DOM APIs.

Instead of allowing an application to assign arbitrary strings to a sink such as:

element.innerHTML = value;

a Content Security Policy can require a trusted value.

Conceptually, the browser distinguishes between:

Untrusted string

and:

TrustedHTML

An application creates a Trusted Types policy that defines how a value becomes trusted.

For example:

const policy = trustedTypes.createPolicy("app", {
  createHTML(value) {
    return sanitize(value);
  }
});

The important part is that sanitize() must actually make the HTML safe.

Trusted Types do not magically sanitize arbitrary HTML.

They establish a controlled boundary where the application decides what is allowed to reach a dangerous DOM sink.

Why Trusted Types Matter in React Applications

React already escapes normal text output.

So why would a React application need Trusted Types?

Because real applications frequently contain code outside normal JSX escaping.

Examples include:

A security problem can occur when one of these paths bypasses React's normal escaping behavior.

Trusted Types can provide an additional browser-enforced layer.

The goal is defense in depth:

User Input
    ↓
Validation / Sanitization
    ↓
Trusted Types Policy
    ↓
DOM Sink

Rather than:

User Input
    ↓
DOM Sink

React's Normal HTML Escaping

Consider:

function Comment({ text }) {
  return <p>{text}</p>;
}

If the user submits:

<script>alert("test")</script>

React renders the value as text.

The browser does not execute it as a script.

This is one reason developers should prefer normal JSX whenever possible.

For example:

<p>{comment.text}</p>

is generally safer than:

<p dangerouslySetInnerHTML={{ __html: comment.text }} />

The second example explicitly asks React to interpret the value as HTML.

The Risk of dangerouslySetInnerHTML

There are legitimate reasons to render HTML.

For example, a CMS may return formatted content:

<h2>Introduction</h2>
<p>This is an article.</p>

An application might render it with:

function Article({ html }) {
  return (
    <article
      dangerouslySetInnerHTML={{
        __html: html
      }}
    />
  );
}

The problem is not the API itself.

The problem is trusting the input.

If html contains attacker-controlled content, the application has created an injection opportunity.

A safer architecture is:

CMS content
    ↓
Sanitization
    ↓
Trusted representation
    ↓
React
    ↓
DOM

Trusted Types can strengthen this boundary by allowing the browser to reject inappropriate values at protected DOM sinks.

Enabling Trusted Types with Content Security Policy

Trusted Types are enforced through Content Security Policy.

A policy can require trusted HTML for relevant DOM sinks.

Conceptually, a CSP can contain:

require-trusted-types-for 'script'

An application can also specify which Trusted Types policies are permitted.

The exact CSP configuration should be designed according to the application's framework, deployment model, third-party scripts, and existing security policy.

Do not copy a security header into production without testing the entire application.

A restrictive policy can expose previously hidden DOM-writing behavior in:

Creating a Trusted Types Policy

A policy should have a narrow purpose.

For example:

const policy = trustedTypes.createPolicy("app-html", {
  createHTML(input) {
    return sanitizeHtml(input);
  }
});

The sanitization function is the critical part.

A policy like this is dangerous:

const policy = trustedTypes.createPolicy("unsafe", {
  createHTML(input) {
    return input;
  }
});

This effectively declares arbitrary input trustworthy.

It defeats the security objective.

Trusted Types should not become a mechanism for simply suppressing browser security errors.

React and TrustedHTML

Modern React can work with Trusted Types values rather than requiring developers to convert every trusted value back into an ordinary string.

A conceptual example is:

function Article({ trustedContent }) {
  return (
    <article
      dangerouslySetInnerHTML={{
        __html: trustedContent
      }}
    />
  );
}

The important security boundary remains the creation of trustedContent.

For example:

const trustedContent = policy.createHTML(
  sanitizedHtml
);

The application should not accept arbitrary HTML and immediately mark it as trusted.

React's Trusted Types support is intended to work with browser security mechanisms without requiring applications to unnecessarily weaken those protections.

Trusted Types Are Not an HTML Sanitizer

This is one of the most important points.

Trusted Types answer:

"Is this value allowed to reach a protected DOM sink?"

They do not inherently answer:

"Is this HTML safe?"

That is the responsibility of the Trusted Types policy.

Consider:

const policy = trustedTypes.createPolicy("app", {
  createHTML(value) {
    return value;
  }
});

This creates a TrustedHTML value but does not make unsafe HTML safe.

A proper implementation needs a trusted sanitization step:

const policy = trustedTypes.createPolicy("app", {
  createHTML(value) {
    return sanitizeHtml(value);
  }
});

The sanitizer itself must be configured correctly for the application's content model.

Trusted Types vs Traditional Input Validation

Trusted Types and input validation solve different problems.

Security technique

Primary purpose

Input validation

Reject invalid input

Output encoding

Prevent data from being interpreted as executable content

HTML sanitization

Remove unsafe HTML

CSP

Restrict browser execution behavior

Trusted Types

Control values reaching sensitive DOM sinks

React escaping

Safely render ordinary text

A strong application can use several of these controls together.

Trusted Types should not be treated as a replacement for authentication, authorization, input validation, sanitization, or secure coding practices.

Handling Third-Party Libraries

Third-party libraries are a common reason to introduce Trusted Types.

Suppose a library contains:

element.innerHTML = generatedHtml;

When Trusted Types enforcement is enabled, that code may fail if the value does not meet the browser's Trusted Types requirements.

This can initially look like a React problem, but the actual issue may be inside a dependency.

A practical troubleshooting process is:

  1. Identify the DOM sink producing the error.

  2. Determine which library or component is calling it.

  3. Check whether the library supports Trusted Types.

  4. Upgrade to a compatible version if available.

  5. Replace the library if it cannot safely operate under the application's security policy.

  6. Avoid creating a broad policy simply to make the library work.

This approach keeps the security boundary intact.

Common Mistakes

Trusting User Input Directly

Avoid:

policy.createHTML(userInput);

unless userInput has passed through an appropriate sanitization process.

Creating a Policy That Returns the Input

Avoid:

createHTML(value) {
  return value;
}

This provides the appearance of Trusted Types protection without actually creating a meaningful trust boundary.

Assuming React Escapes dangerouslySetInnerHTML

It does not.

This API intentionally bypasses normal text escaping.

<div
  dangerouslySetInnerHTML={{
    __html: html
  }}
/>

The input must therefore be controlled and appropriately sanitized.

Ignoring Non-React DOM Code

A React application can still contain:

element.innerHTML = value;

or third-party code that performs equivalent operations.

Security reviews should cover the entire browser application, not only JSX.

Allowing Too Many Trusted Types Policies

A large collection of policies can make security difficult to understand.

Prefer a small number of clearly named policies with well-defined responsibilities.

Troubleshooting Trusted Types Errors

A common browser error indicates that a value cannot be assigned to a protected DOM sink.

The first step is to identify the sink.

Look for operations such as:

innerHTML
outerHTML
insertAdjacentHTML

and related APIs.

Then determine where the value originated.

For example:

CMS content
   ↓
Markdown renderer
   ↓
HTML string
   ↓
innerHTML

The security review should focus on the transition between those stages.

If the content is legitimate HTML, introduce a controlled sanitization and Trusted Types policy.

If the content does not need to be HTML, the better solution may be to render it as normal text.

Testing a Trusted Types Policy

Security controls should be tested before enforcement is enabled broadly.

Start by identifying existing DOM sinks.

Then test important application flows:

Pay particular attention to functionality that dynamically creates HTML.

A Content Security Policy can also be introduced in a reporting or controlled rollout strategy before strict enforcement, depending on the application's security deployment process.

Production Best Practices

Prefer Normal JSX

Whenever possible:

<p>{content}</p>

is preferable to:

<p dangerouslySetInnerHTML={{ __html: content }} />

Sanitize HTML at a Defined Boundary

Do not scatter sanitization logic throughout components.

Create a clear security boundary for HTML content.

Keep Trusted Types Policies Small

Each policy should have a clear reason to exist.

Audit DOM Sinks

Search application code and dependencies for dangerous DOM APIs.

Test Third-Party Components

A strict Trusted Types policy can expose unsafe assumptions in dependencies.

Combine Security Controls

Use Trusted Types alongside appropriate:

No single browser security feature eliminates every injection vulnerability.

Advantages and Disadvantages

Advantages

Disadvantages

When Should You Use Trusted Types?

Trusted Types are particularly valuable for applications that:

For a simple React application that only renders ordinary strings through JSX, the immediate need may be lower because React already escapes those values.

However, as an application grows, more DOM-writing paths often appear. Trusted Types can then provide a useful additional security layer.

Conclusion

React's default escaping provides an important defense against injection attacks, but real applications often contain situations where developers intentionally work with HTML or browser DOM APIs.

Trusted Types provide a browser-enforced boundary around those sensitive operations.

The most important thing to remember is that Trusted Types are not a replacement for sanitization. A Trusted Types policy should only create trusted values after the application has established that the content is safe for its intended context.

For production React applications, a strong approach is to prefer ordinary JSX text rendering, isolate unavoidable HTML rendering, sanitize content at a defined boundary, keep Trusted Types policies narrow, and test third-party dependencies before enforcing strict policies.

Used this way, Trusted Types complement React's existing security model rather than replacing it, giving applications another layer of protection against DOM injection.