--- createdAt: 2026-01-20 updatedAt: 2026-09-27 priority: 8 title: "HTML Content with Custom Components" description: "Declare HTML content in Intlayer and replace tags with your own components at render time, for rich translated text without dangerouslySetInnerHTML." keywords: - HTML - Custom Components - Rich Content - Intlayer - Next.js - JavaScript - React - Vue - Svelte - Remix - Astro slugs: - doc - concept - content - html history: - version: 8.5.0 date: 2026-03-24 changes: "Add `intlayerHTML` plugin object; use `app.use(intlayerHTML)` instead of `app.use(installIntlayerHTML)`" - version: 8.5.0 date: 2026-03-24 changes: "move import from `{{framework}}-intlayer` to `{{framework}}-intlayer/html`" - version: 8.0.0 date: 2026-01-22 changes: "Add HTMLRenderer / useHTMLRenderer / renderHTML utility" - version: 8.0.0 date: 2026-01-20 changes: "Add HTML parsing support" author: aymericzip --- # HTML Content / HTML in Intlayer Intlayer supports HTML content, allowing you to embed rich, structured content within your dictionaries. This content can be rendered with standard HTML tags or replaced with custom components at runtime. ## Declaring HTML Content You can declare HTML content using the `html` function or simply as a string. Use the `html` function to explicitly declare HTML content. This ensures standard tags are mapped correctly even if automatic detection is disabled. ```typescript fileName="htmlDictionary.content.ts" import { html, type Dictionary } from "intlayer"; const htmlDictionary = { key: "app", contentAutoTransformation: true, // can be set in config file content: { myHtmlContent: html("

Hello World

"), }, } satisfies Dictionary; export default htmlDictionary; ```
If the string contains common HTML tags (e.g., `

`, `

`, ``, etc.), Intlayer will automatically transform it. ```typescript fileName="htmlDictionary.content.ts" export default { key: "app", contentAutoTransformation: true, // can be set in config file content: { myHtmlContent: "

Hello World

", }, }; ``` Import HTML content from files. Note that currently `file()` function returns a string, which will be auto-detected as HTML if it contains tags. ```typescript fileName="htmlDictionary.content.ts" import { html, file, t } from "intlayer"; export default { key: "app", content: { content: t({ en: html(file("./content.en.html")), fr: html(file("./content.fr.html")), }), }, }; ``` ### The `html()` Node The `html()` function is a new feature in Intlayer v8 that allows you to explicitly define HTML content in your dictionaries. While Intlayer can often auto-detect HTML content, using the `html()` function provides several advantages: - **Type Safety**: The `html()` function allows you to define the expected props for custom components, providing better autocompletion and type checking in your editor. - **Explicit Declaration**: It ensures that a string is always treated as HTML, even if it doesn't contain standard HTML tags that would trigger auto-detection. - **Custom Component Definition**: You can pass a second argument to `html()` to define the custom components and their expected prop types. ```typescript import { html } from "intlayer"; const myContent = html( "World", { MyCustomComponent: { title: "string", children: "node", }, } ); ``` When using the `.use()` method on an HTML node, the components you provide will be checked against the definition provided in the `html()` function (if available). ## Rendering HTML Rendering can be handled automatically by Intlayer's content system or manually using specialized tools. ### Automatic Rendering (using `useIntlayer`) When you access content via `useIntlayer`, HTML nodes are already prepared for rendering. HTML nodes can be rendered directly as JSX. Standard tags work automatically. ```tsx fileName="App.tsx" import { useIntlayer } from "react-intlayer"; const AppContent = () => { const { myHtmlContent } = useIntlayer("app"); return
{myHtmlContent}
; }; ``` Use the `.use()` method to provide custom components or override tags: ```tsx {myHtmlContent.use({ p: (props) =>

, CustomLink: ({ children }) => {children}, })} ``` In Vue, HTML content can be rendered using the `component` built-in. ```vue fileName="App.vue" ``` Use `.use()` for overrides: ```vue ``` Svelte renders HTML nodes as strings. Use `{@html}` to render it. ```svelte {@html $content.myHtmlContent} ``` Preact supports HTML nodes directly in the JSX. ```tsx fileName="App.tsx" import { useIntlayer } from "preact-intlayer"; const AppContent = () => { const { myHtmlContent } = useIntlayer("app"); return

{myHtmlContent}
; }; ```
Solid supports HTML nodes directly in the JSX. ```tsx fileName="App.tsx" import { useIntlayer } from "solid-intlayer"; const AppContent = () => { const { myHtmlContent } = useIntlayer("app"); return
{myHtmlContent}
; }; ```
Angular uses the `[innerHTML]` directive to render HTML content. ```typescript fileName="app.component.ts" import { Component } from "@angular/core"; import { useIntlayer } from "angular-intlayer"; @Component({ selector: "app-root", template: `
`, }) export class AppComponent { content = useIntlayer("app"); } ``` Use the `.use()` method to provide custom components or override tags: ```typescript content().myHtmlContent.use({ p: { class: "prose" }, CustomLink: { href: "/details" }, }) ```
In Remix 3, HTML nodes resolve to an HTML string. Inject it with the `innerHTML` prop of Remix JSX, or with `html.raw` in an `html-template` view. ```tsx fileName="src/views/home.tsx" import { useIntlayer } from "remix-intlayer"; export const HomePage = () => () => { const { myHtmlContent } = useIntlayer("app"); return
; }; ``` ```ts fileName="src/views/home.ts" import { html } from "remix/html-template"; import { useIntlayer } from "remix-intlayer"; export const renderHomePage = () => { const { myHtmlContent } = useIntlayer("app"); return html.raw`
${myHtmlContent.value}
`; }; ``` > Remix escapes interpolated values by default. `innerHTML` and `html.raw` are the two opt-outs, which is what an HTML string needs. Use the `.use()` method to override tags or map custom components. Overrides are functions returning an HTML string: ```tsx
`

${children}

`, CustomLink: ({ children }) => `${children}`, })} /> ``` In Astro, HTML nodes resolve to an HTML string. Inject it with the `set:html` directive in the template, or with `innerHTML` in a client ` ``` > Astro escapes `{expressions}` by default. `set:html` is the opt-out, which is what an HTML string needs. Use the `.use()` method to override tags or map custom components. Overrides are functions returning an HTML string: ```astro
`

${children}

`, CustomLink: ({ children }) => `${children}`, })} /> ``` ## Global Configuration with `HTMLProvider` You can configure HTML rendering globally for your entire application. This is ideal for defining custom components that should be available in all HTML content. ```tsx fileName="AppProvider.tsx" import { HTMLProvider } from "react-intlayer/html"; export const AppProvider = ({ children }) => (

, CustomLink: ({ children }) => {children}, }} > {children} ); ``` You can also use your own HTML renderer: ```tsx fileName="AppProvider.tsx" import { HTMLProvider } from "react-intlayer/html"; export const AppProvider = ({ children }) => ( { const { renderHTML } = await import('react-intlayer/html'); return renderHTML(html); }} > {children} ); ``` > Importing your HTML renderer dynamically is a good way to reduce the bundle size of your application. ```typescript fileName="main.ts" import { createApp, h } from "vue"; import { intlayer } from "vue-intlayer"; import { intlayerHTML } from "vue-intlayer/html"; import App from "./App.vue"; const app = createApp(App); app.use(intlayer); app.use(intlayerHTML, { components: { p: (props, { slots }) => h("p", { class: "prose", ...props }, slots.default?.()), CustomLink: (props, { slots }) => h("a", { href: "/details", ...props }, slots.default?.()), }, }); app.mount("#app"); ``` You can also use your own HTML renderer: ```typescript fileName="main.ts" import { createApp, h } from "vue"; import { intlayer } from "vue-intlayer"; import { intlayerHTML } from "vue-intlayer/html"; import App from "./App.vue"; const app = createApp(App); app.use(intlayer); app.use(intlayerHTML, { renderHTML: async (html) => { const { renderHTML } = await import('vue-intlayer/html'); return renderHTML(html); }, }); app.mount("#app"); ``` > Importing your HTML renderer dynamically is a good way to reduce the bundle size of your application. ```svelte fileName="App.svelte" ``` You can also use your own HTML renderer: ```svelte fileName="App.svelte" { const { renderHTML } = await import('svelte-intlayer/html'); return renderHTML(html); }} > ``` > Importing your HTML renderer dynamically is a good way to reduce the bundle size of your application. ```tsx fileName="AppProvider.tsx" import { HTMLProvider } from "preact-intlayer/html"; export const AppProvider = ({ children }) => (

, }} > {children} ); ``` You can also use your own HTML renderer: ```tsx fileName="AppProvider.tsx" import { HTMLProvider } from "preact-intlayer/html"; export const AppProvider = ({ children }) => ( { const { renderHTML } = await import('preact-intlayer/html'); return renderHTML(html); }} > {children} ); ``` > Importing your HTML renderer dynamically is a good way to reduce the bundle size of your application. ```tsx fileName="AppProvider.tsx" import { HTMLProvider } from "solid-intlayer/html"; export const AppProvider = (props) => (

, }} > {props.children} ); ``` You can also use your own HTML renderer: ```tsx fileName="AppProvider.tsx" import { HTMLProvider } from "solid-intlayer/html"; export const AppProvider = (props) => ( { const { renderHTML } = await import('solid-intlayer/html'); return renderHTML(html); }} > {props.children} ); ``` > Importing your HTML renderer dynamically is a good way to reduce the bundle size of your application. ```typescript fileName="app.config.ts" import { createIntlayerHTMLProvider } from "angular-intlayer/html"; export const appConfig: ApplicationConfig = { providers: [ createIntlayerHTMLProvider({ components: { p: { class: "prose" }, CustomLink: { href: "/details" }, }, }), ], }; ``` You can also use your own HTML renderer: ```typescript fileName="app.config.ts" import { createIntlayerHTMLProvider } from "angular-intlayer/html"; export const appConfig: ApplicationConfig = { providers: [ createIntlayerHTMLProvider({ renderHTML: async (html) => { const { renderHTML } = await import('angular-intlayer/html'); return renderHTML(html); }, }), ], }; ``` > Importing your HTML renderer dynamically is a good way to reduce the bundle size of your application. Remix has no component tree to hold a provider, so the configuration is installed once, as a singleton, at server start. It configures the renderer returned by `useHTMLRenderer()`. The `html` nodes returned by `useIntlayer` are rendered as-is; override their tags per node with `.use()`. ```typescript fileName="src/router.ts" import { installIntlayerHTML } from "remix-intlayer/html"; installIntlayerHTML({ renderHTML: (html) => html.replaceAll("

", '

'), }); ``` > Use `installIntlayerHTMLDynamic(async () => …)` to load the renderer itself lazily; the loader runs on the first call only. Astro has no component tree to hold a provider, so the configuration is installed once, as a singleton, in the middleware (server) and in a client ` ``` #### `` Component ```svelte ``` #### `useHTMLRenderer()` Hook ```svelte {@html render("

Hello World

")} ``` #### `renderHTML()` Utility ```svelte {@html renderHTML("

Hello World

")} ```
#### `` Component ```tsx import { HTMLRenderer } from "preact-intlayer/html"; {"

Hello World

"}
``` #### `useHTMLRenderer()` Hook ```tsx import { useHTMLRenderer } from "preact-intlayer/html"; const render = useHTMLRenderer(); return
{render("

Hello World

")}
; ``` #### `renderHTML()` Utility ```tsx import { renderHTML } from "preact-intlayer/html"; return
{renderHTML("

Hello World

")}
; ```
#### `` Component ```tsx import { HTMLRenderer } from "solid-intlayer/html"; {"

Hello World

"}
``` #### `useHTMLRenderer()` Hook ```tsx import { useHTMLRenderer } from "solid-intlayer/html"; const render = useHTMLRenderer(); return
{render("

Hello World

")}
; ``` #### `renderHTML()` Utility ```tsx import { renderHTML } from "solid-intlayer/html"; return
{renderHTML("

Hello World

")}
; ```
#### `IntlayerHTMLService` Service Render an HTML string using the service. ```typescript import { IntlayerHTMLService } from "angular-intlayer/html"; export class MyComponent { constructor(private markdownService: IntlayerHTMLService) {} renderHTML(html: string) { return this.markdownService.renderHTML(html); } } ``` #### `useHTMLRenderer()` Hook Get a renderer function pre-configured by `installIntlayerHTML()`. It returns an HTML string. ```tsx import { useHTMLRenderer } from "remix-intlayer/html"; const renderHTML = useHTMLRenderer(); return
Hello World

")} />; ``` #### `renderHTML()` Utility Standalone utility that ignores the global configuration. ```tsx import { renderHTML } from "remix-intlayer/html"; const html = renderHTML("

Hello

"); ``` #### `useHTMLRenderer()` Hook Get a renderer function pre-configured by `installIntlayerHTML()`. It returns an HTML string. ```astro --- import { useHTMLRenderer } from "astro-intlayer/html"; const renderHTML = useHTMLRenderer(); ---
Hello World

")} /> ``` #### `renderHTML()` Utility Standalone utility that ignores the global configuration. ```astro --- import { renderHTML } from "astro-intlayer/html"; const html = renderHTML("

Hello

"); ---
``` ## Options Reference These options can be passed to `HTMLProvider`, `HTMLRenderer`, `useHTMLRenderer`, and `renderHTML`. | Option | Type | Default | Description | | :----------- | :-------------------- | :------ | :------------------------------------------------------------------------------------------------------------------ | | `components` | `Record` | `{}` | A map of HTML tags or custom component names to components. | | `renderHTML` | `Function` | `null` | A custom rendering function to completely replace the default HTML parser (Vue, Svelte, Remix and Astro providers). | > Note: For React and Preact, standard HTML tags are automatically provided. You only need to pass the `components` prop if you want to override them or add custom components.