# content-rendering
> WHAT: Safe HTML rendering with RenderSanitizedHTML and XSS prevention. WHEN: rendering CMS content, translated HTML, user-generated rich text. KEYWORDS: html, render, sanitize, xss, RenderSanitizedHTML, sanitize-html, truncation, content.
- Author: João Prado
- Repository: guicheffer/devorch-cli
- Version: 20260122234748
- Stars: 1
- Forks: 0
- Last Updated: 2026-02-06
- Source: https://github.com/guicheffer/devorch-cli
- Web: https://mule.run/skillshub/@@guicheffer/devorch-cli~content-rendering:20260122234748
---
---
name: content-rendering
description: "WHAT: Safe HTML rendering with RenderSanitizedHTML and XSS prevention. WHEN: rendering CMS content, translated HTML, user-generated rich text. KEYWORDS: html, render, sanitize, xss, RenderSanitizedHTML, sanitize-html, truncation, content."
---
# Content Rendering
## Documentation
This skill has comprehensive documentation:
- **[Production Examples](./references/examples.md)** - Real-world code examples from the codebase
- **[API Reference](./references/api-docs.md)** - Complete API documentation with official links
- **[Implementation Patterns](./references/patterns.md)** - Best practices and anti-patterns
## Core Principles
**Always sanitize HTML before rendering.** User-generated HTML content must pass through sanitize-html before rendering with react-native-render-html. Never render unsanitized HTML directly - this prevents XSS attacks and malicious script injection.
**Always wrap RenderSanitizedHTML with ErrorBoundary.** HTML rendering can fail for malformed content or edge cases. ErrorBoundary ensures graceful degradation without crashing the entire app.
**Always sanitize before truncating.** Security comes before UX. The order is: (1) sanitize HTML to remove dangerous content, (2) truncate to maxWords limit, (3) render. This ensures malicious content is removed even if truncation fails.
**Always use custom sanitization configs for specific content types.** Different content types need different sanitization rules. Marketing content may allow more tags than user comments. Define allowedTags and allowedAttributes explicitly for each use case.
**Why**: Safe HTML rendering enables rich text content (bold, italic, lists, links) from CMS and translations, prevents security vulnerabilities through XSS prevention, provides flexible content truncation for long text, and maintains app stability through error boundaries.
## When to Use This Skill
Use these patterns when:
- Rendering HTML content from CMS or feature content APIs
- Displaying translated strings that contain HTML formatting (bold, italic, lists)
- Showing user-generated content with rich text formatting
- Rendering product descriptions or marketing copy with HTML
- Displaying sustainability information or educational content
- Rendering recipe instructions or cooking tips with formatting
- Showing upsell messages or promotional content from backend
- Building empty states with formatted body text
- Implementing "read more" truncation for long HTML content
- Rendering email templates or notification messages with HTML
## RenderSanitizedHTML Component
### Basic Usage
Use RenderSanitizedHTML for safe HTML rendering with default sanitization.
```typescript
import { RenderSanitizedHTML } from '@libs/render-sanitized-html';
const ProductDescription = ({ product }) => {
return (
);
};
```
**Why**: RenderSanitizedHTML wraps react-native-render-html with automatic sanitization. Default config removes dangerous tags (script, iframe) while preserving safe formatting (p, strong, em, ul, li). ErrorBoundary handles rendering failures gracefully.
**Production Example**: `modules/social-recipe-bridge/screens/social-recipe-bridge/components/empty-state/EmptyStateView.tsx:22`
### Component API
RenderSanitizedHTML accepts four props: source, style, config, maxWords.
```typescript
Hello World
' }}
style={styles.bodyText}
config={{
allowedTags: ['p', 'br', 'strong', 'em'],
allowedAttributes: {},
}}
maxWords={50}
/>
```
**Props:**
- `source` - Required. Object with `html` string property (HTMLSourceInline type from react-native-render-html)
- `style` - Optional. Custom styles merged with baseStyle from Zest theme (MixedStyleDeclaration type)
- `config` - Optional. Custom sanitization rules (sanitize.IOptions type from sanitize-html)
- `maxWords` - Optional. Maximum word count before truncation with ellipsis
**Why**: source prop matches react-native-render-html API for consistency. style prop enables Zest theme integration. config prop allows custom sanitization per use case. maxWords enables "read more" patterns.
### With Custom Styling
Merge custom styles with Zest theme for branded typography.
```typescript
import { useZestStyles } from '@zest/react-native';
import { RenderSanitizedHTML } from '@libs/render-sanitized-html';
const MarketingBanner = ({ content }) => {
const styles = useZestStyles(stylesConfig);
return (
);
};
const stylesConfig = createStylesConfig((theme) => ({
bodyText: {
color: theme.colors.alias.text.secondary,
fontSize: theme.typography['body-md-regular'].fontSize,
lineHeight: theme.typography['body-md-regular'].lineHeight,
},
}));
```
**Why**: Custom styles enable brand-consistent typography while preserving HTML formatting. Zest theme tokens ensure visual consistency across features. Style merging combines theme defaults with custom overrides.
**Production Example**: `modules/store/screens/cart/components/upsell-nudge-button/UpsellNudgeButton.tsx:40`
## HTML Sanitization
### Default Sanitization
RenderSanitizedHTML uses sanitize-html default config for basic security.
```typescript
// Automatically sanitizes:
// ❌ Removes:
';
render();
expect(screen.getByText('Click me')).toBeTruthy();
// onclick attribute should be removed by sanitization
});
});
```
**Why**: XSS prevention is critical security feature. Test that script tags are removed. Test that event handler attributes (onclick, onerror) are removed. Use queryByText for null checks. Regex matchers (/Safe content/) handle partial matches.
**Production Example**: `libs/render-sanitized-html/RenderSanitizedHTML.test.tsx:31`
### Test Custom Sanitization Config
Test that custom configs restrict allowed tags correctly.
```typescript
describe('Custom Sanitization Config', () => {
it('applies restrictive config to remove tags', () => {
const htmlContent = '
Bold and italic
';
const restrictiveConfig = {
allowedTags: ['p'], // Only allow p tags
allowedAttributes: {},
};
render(
);
// Text content preserved, but strong and em tags removed
expect(screen.getByText(/Bold and italic/)).toBeTruthy();
});
});
```
**Why**: Custom configs should restrict tags as specified. Test allowedTags removes unwanted tags. Test text content is preserved after tag removal. Verifies config is passed to sanitize-html correctly.
**Production Example**: `libs/render-sanitized-html/RenderSanitizedHTML.test.tsx:43`
### Test maxWords Truncation
Test that truncation works correctly with maxWords prop.
```typescript
describe('maxWords Truncation', () => {
const longHtml =
'
This is a very long paragraph with many words that should be truncated.
';
it('truncates when maxWords is exceeded', () => {
render(
);
expect(screen.getByText(/\.\.\./)).toBeTruthy();
expect(screen.queryByText(/should be truncated/)).toBeNull();
});
it('does not truncate when content is shorter than maxWords', () => {
const shortHtml = '
Short content
';
render(
);
expect(screen.getByText(/Short content/)).toBeTruthy();
expect(screen.queryByText(/\.\.\./)).toBeNull();
});
it('ignores HTML tags when counting words', () => {
const htmlWithTags =
'
Onetwothree
';
render(
);
expect(screen.getByText(/One.*two.*\.\.\./)).toBeTruthy();
expect(screen.queryByText(/three/)).toBeNull();
});
});
```
**Why**: Truncation should respect maxWords limit. Test ellipsis appears when truncated. Test no ellipsis when content is short. Test HTML tags ignored when counting words. Regex matchers verify partial content.
**Production Example**: `libs/render-sanitized-html/RenderSanitizedHTML.test.tsx:88`
### Test needTruncating Helper
Test helper function for checking truncation necessity.
```typescript
import { needTruncating } from '@libs/render-sanitized-html';
describe('needTruncating', () => {
it('returns true when text exceeds maxWords', () => {
const longText = '
This is a very long text with many words
';
const result = needTruncating(longText, 5);
expect(result).toBe(true);
});
it('returns false when text is within maxWords', () => {
const shortText = '
Short text
';
const result = needTruncating(shortText, 5);
expect(result).toBe(false);
});
it('ignores HTML tags when counting words', () => {
const htmlText = '
Onetwo
';
const result = needTruncating(htmlText, 5);
expect(result).toBe(false); // Only 2 words
});
});
```
**Why**: Test helper function separately from component. Test true when exceeds limit. Test false when within limit. Test HTML tags ignored. Unit test pure function for fast feedback.
**Production Example**: `libs/render-sanitized-html/RenderSanitizedHTML.test.tsx:337`
## Common Mistakes to Avoid
❌ **Don't render unsanitized HTML**:
```typescript
// ❌ Wrong - no sanitization
import RenderHTML from 'react-native-render-html';
```
**Why**: Unsanitized HTML enables XSS attacks. Malicious users can inject script tags. No protection against dangerous content.
✅ **Do use RenderSanitizedHTML**:
```typescript
// ✅ Correct - automatic sanitization
import { RenderSanitizedHTML } from '@libs/render-sanitized-html';
```
**Why**: RenderSanitizedHTML automatically sanitizes with sanitize-html. Removes script tags and dangerous attributes. Secure by default.
❌ **Don't truncate before sanitizing**:
```typescript
// ❌ Wrong - truncate first, sanitize second
const truncatedHtml = truncate(html, maxWords);
const sanitizedHtml = sanitize(truncatedHtml);
```
**Why**: Truncation may break malicious HTML in unexpected ways. Script tags could survive truncation. Security should always come first.
✅ **Do sanitize before truncating**:
```typescript
// ✅ Correct - sanitize first, truncate second
const sanitizedHtml = sanitize(html, config);
const truncatedHtml = truncate(sanitizedHtml, maxWords);
```
**Why**: Sanitization removes dangerous content first. Truncation operates on safe HTML. Security before UX.
❌ **Don't skip ErrorBoundary**:
```typescript
// ❌ Wrong - no error handling
```
**Why**: HTML rendering can crash for malformed content. No graceful degradation. Entire app crashes on rendering errors.
✅ **Do wrap with ErrorBoundary**:
```typescript
// ✅ Correct - graceful error handling
```
**Why**: ErrorBoundary catches rendering errors. App continues working after errors. Component returns null for graceful degradation.
❌ **Don't use overly permissive sanitization**:
```typescript
// ❌ Wrong - too permissive
const permissiveConfig = {
allowedTags: sanitize.defaults.allowedTags.concat(['style', 'link']),
allowedAttributes: {
'*': ['style', 'class', 'id'], // All elements, all attributes
},
};
```
**Why**: Permissive configs enable attacks through CSS injection. class and id attributes can target malicious styles. style attribute enables arbitrary CSS.
✅ **Do use restrictive configs for user content**:
```typescript
// ✅ Correct - restrictive for user content
const restrictiveConfig = {
allowedTags: ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li'],
allowedAttributes: {}, // No attributes
};
```
**Why**: Restrictive configs minimize attack surface. Only allow necessary formatting tags. No attributes prevents CSS injection. User content should be most restrictive.
❌ **Don't forget contentWidth**:
```typescript
// ❌ Wrong - missing contentWidth
```
**Why**: contentWidth is required by react-native-render-html. Component may crash without it. Layout issues on different screen sizes.
✅ **Do use useContentWidth**:
```typescript
// ✅ Correct - responsive width
import { useContentWidth } from 'react-native-render-html';
const width = useContentWidth();
```
**Why**: useContentWidth provides responsive width. Adapts to available space. Required for proper HTML layout.
## Quick Reference
**Basic usage**:
```typescript
import { RenderSanitizedHTML } from '@libs/render-sanitized-html';
Hello World' }}
/>
```
**With custom styling**:
```typescript
const styles = useZestStyles(stylesConfig);
```
**With custom sanitization**:
```typescript
const config = {
allowedTags: ['p', 'br', 'strong', 'em'],
allowedAttributes: {},
};
```
**With truncation**:
```typescript
```
**Check if truncation needed**:
```typescript
import { needTruncating } from '@libs/render-sanitized-html';
const shouldTruncate = needTruncating(html, 50);
{shouldTruncate && Read more}
```
**With translated content**:
```typescript
const { translateRaw } = useT9n('feature');
```
**Testing XSS prevention**:
```typescript
it('removes script tags', () => {
const maliciousHtml = '