React may warn when using dangerouslySetInnerHTML with HTML containing style tags due to security risks and improper DOM handling. This warning indicates potential XSS vulnerabilities or hydration mismatches when injecting unsanitized HTML with embedded styles.
When you use React's dangerouslySetInnerHTML prop to inject HTML that contains <style> tags, you may encounter browser console warnings or unexpected behavior. This occurs because dangerouslySetInnerHTML bypasses React's built-in sanitization and virtual DOM reconciliation, creating a "black box" that React cannot optimize or validate. The most common warnings related to this pattern are "dangerouslySetInnerHTML did not match" (during server-side rendering/hydration) and security warnings about potential XSS vulnerabilities. Style tags injected via dangerouslySetInnerHTML are particularly problematic because they can contain malicious CSS that manipulates page layout, steals data through background-image URLs, or enables clickjacking attacks. React intentionally names this prop "dangerously" to warn developers that using it without proper sanitization exposes applications to cross-site scripting (XSS) attacks. When HTML containing style tags comes from user input or external sources, attackers can inject malicious styles or scripts that execute in other users' browsers.
The most critical step is to sanitize any HTML before passing it to dangerouslySetInnerHTML. Install DOMPurify:
npm install dompurify
npm install --save-dev @types/dompurifyThen sanitize the HTML content:
import DOMPurify from 'dompurify';
function SafeHtmlComponent({ htmlContent }: { htmlContent: string }) {
const sanitizedHtml = DOMPurify.sanitize(htmlContent, {
ALLOWED_TAGS: ['p', 'b', 'i', 'em', 'strong', 'a', 'div', 'span'],
ALLOWED_ATTR: ['href', 'class', 'id'],
// Remove style tags for security
FORBID_TAGS: ['style', 'script'],
FORBID_ATTR: ['style', 'onerror', 'onclick']
});
return <div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />;
}This removes potentially dangerous elements including style tags and inline event handlers, preventing XSS attacks.
Instead of injecting HTML with style tags, extract the styles and apply them using React's style prop or CSS classes:
// ❌ Avoid this - dangerouslySetInnerHTML with style tags
const htmlWithStyles = '<style>.highlight { color: red; }</style><p class="highlight">Text</p>';
<div dangerouslySetInnerHTML={{ __html: htmlWithStyles }} />;
// ✅ Use this instead - external CSS or CSS modules
import styles from './component.module.css';
<p className={styles.highlight}>Text</p>;
// ✅ Or use inline styles as objects
<p style={{ color: 'red' }}>Text</p>;This approach is safer, more performant, and works seamlessly with React's virtual DOM.
Use a library like html-react-parser to convert HTML strings into React components, which provides better control and security:
npm install html-react-parserimport parse from 'html-react-parser';
function SafeHtmlParser({ htmlContent }: { htmlContent: string }) {
// Parse HTML and replace style tags with safe alternatives
const content = parse(htmlContent, {
replace: (domNode: any) => {
// Block style tags entirely
if (domNode.name === 'style') {
return <></>;
}
// Block script tags
if (domNode.name === 'script') {
return <></>;
}
}
});
return <div>{content}</div>;
}This gives you programmatic control over which elements are rendered and allows you to filter out dangerous tags.
If you're using Next.js or another SSR framework and see "dangerouslySetInnerHTML did not match" warnings:
'use client';
import { useEffect, useState } from 'react';
function ClientOnlyHtml({ htmlContent }: { htmlContent: string }) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return null; // Don't render on server
}
return <div dangerouslySetInnerHTML={{ __html: htmlContent }} />;
}This ensures the HTML only renders on the client, avoiding hydration mismatches. However, this sacrifices SEO benefits of SSR.
Encapsulate HTML sanitization in a reusable component that all developers can use, and configure code scanning tools to flag raw dangerouslySetInnerHTML usage:
// components/SafeHtml.tsx
import DOMPurify from 'dompurify';
interface SafeHtmlProps {
html: string;
className?: string;
allowStyles?: boolean;
}
export function SafeHtml({ html, className, allowStyles = false }: SafeHtmlProps) {
const sanitized = DOMPurify.sanitize(html, {
FORBID_TAGS: allowStyles ? ['script'] : ['script', 'style'],
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover']
});
return (
<div
className={className}
dangerouslySetInnerHTML={{ __html: sanitized }}
/>
);
}Now developers use SafeHtml component instead of dangerouslySetInnerHTML directly: <SafeHtml html={content} />
Configure your server or Next.js to send CSP headers that block unsafe inline styles:
// next.config.js
const securityHeaders = [
{
key: 'Content-Security-Policy',
value: "default-src 'self'; style-src 'self' 'nonce-{random}'; script-src 'self'"
}
];
module.exports = {
async headers() {
return [
{
source: '/:path*',
headers: securityHeaders,
},
];
},
};This provides defense-in-depth by blocking execution of injected malicious styles even if sanitization fails.
Install and configure ESLint to warn developers when they use dangerouslySetInnerHTML:
npm install --save-dev eslint-plugin-reactIn .eslintrc.json:
{
"rules": {
"react/no-danger": "warn",
"react/no-danger-with-children": "error"
}
}This creates a code review checkpoint where usage must be justified and reviewed for security.
Security implications of CSS injection: Even sanitized style tags can be dangerous. Malicious CSS can steal data using techniques like background-image URLs pointing to attacker servers, attribute selectors that leak form values, or animations that trigger on specific user states. Always prefer removing style tags entirely over attempting to sanitize them.
Server-side rendering considerations: When using dangerouslySetInnerHTML in SSR environments like Next.js, ensure the HTML string is identical on server and client. Differences in timezone formatting, random IDs, or client-only browser APIs will cause hydration warnings. Use suppressHydrationWarning only as a last resort.
DOMPurify configuration for different contexts: DOMPurify offers fine-grained control through options like SAFE_FOR_TEMPLATES, RETURN_DOM_FRAGMENT, and custom hooks. For trusted HTML sources (like your own CMS), you might allow more tags. For user-generated content, use the strictest settings with ALLOWED_TAGS whitelist.
Performance implications: Using dangerouslySetInnerHTML creates a "black box" that React cannot optimize. Every time the HTML string changes, React must replace the entire innerHTML, destroying and recreating all DOM nodes. This is slower than React's normal diffing algorithm. For large HTML strings that change frequently, consider parsing into React components.
Browser differences: Some browsers may handle style tags injected via innerHTML differently. Legacy versions of Internet Explorer had security restrictions on dynamically created style elements. Modern browsers generally support it, but the timing of when styles apply can vary.
Alternative: Use a markdown parser: If your content source is markdown or similar, use a React markdown renderer like react-markdown instead of converting to HTML and using dangerouslySetInnerHTML. This provides better security and React integration.
Prop spreading could cause security issues
Prop spreading could cause security issues
Error: error:0308010C:digital envelope routines::unsupported
Error: error:0308010C:digital envelope routines::unsupported
React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render.
React Hook useEffect placed inside a condition
Hook can only be called inside the body of a function component
Hook can only be called inside the body of a function component
Rollup failed to resolve import during build
How to fix "Rollup failed to resolve import" in React