Skip to content
Ilha
Esc
navigateopen⌘Jpreview
On this page

Helpers

Mount islands, build safe HTML strings without JSX, and render explicitly trusted markup.

Mount islands

Auto-discovers all [data-ilha] elements in the DOM and mounts the matching island from a registry. This is the recommended way to activate islands on a page, especially when using SSR and hydration.

Basic usage

import { const ilha: IlhaFactoryilha, const mount: (registry: IslandRegistry, options?: MountOptions) => MountResultmount } from "ilha";

const const Counter: Island<unknown>Counter = ilha<unknown>(component: IslandComponent<unknown>): Island<unknown> (+3 overloads)ilha(() => "<p>counter</p>");
const const Card: Island<unknown>Card = ilha<unknown>(component: IslandComponent<unknown>): Island<unknown> (+3 overloads)ilha(() => "<p>card</p>");

function mount(registry: IslandRegistry, options?: MountOptions): MountResultmount({ type Counter: Island<unknown>Counter, type Card: Island<unknown>Card });

Each key in the registry maps to a data-ilha attribute value in the HTML:

<div data-ilha="Counter"></div>
<div data-ilha="Card"></div>

Options

import { const ilha: IlhaFactoryilha, const mount: (registry: IslandRegistry, options?: MountOptions) => MountResultmount } from "ilha";

const const Counter: Island<unknown>Counter = ilha<unknown>(component: IslandComponent<unknown>): Island<unknown> (+3 overloads)ilha(() => "<p>counter</p>");

const { const unmount: () => void | Promise<void>unmount } = function mount(registry: IslandRegistry, options?: MountOptions): MountResultmount(
  { counter: Island<unknown>counter: const Counter: Island<unknown>Counter },
  {
    MountOptions.root?: Element | undefinedroot: var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.getElementById(elementId: string): HTMLElement | null
The **`getElementById()`** method of the Document interface returns an Element object representing the element whose id property matches the specified string. Since element IDs are required to be unique if specified, they're a useful way to get access to a specific element quickly.
getElementById
("app")!, // default: document.body
MountOptions.lazy?: boolean | undefinedlazy: true, // mount on visibility }, );
Option Type Default Description
root Element document.body Scope discovery to a subtree
lazy boolean false Use IntersectionObserver to mount on visibility

Unmounting

mount() returns an object with an unmount function that tears down all discovered islands at once:

import { const ilha: IlhaFactoryilha, const mount: (registry: IslandRegistry, options?: MountOptions) => MountResultmount } from "ilha";

const const Counter: Island<unknown>Counter = ilha<unknown>(component: IslandComponent<unknown>): Island<unknown> (+3 overloads)ilha(() => "<p>counter</p>");

const { const unmount: () => void | Promise<void>unmount } = function mount(registry: IslandRegistry, options?: MountOptions): MountResultmount({ type Counter: Island<unknown>Counter });

// Later — stops all effects, removes all listeners
const unmount: () => void | Promise<void>unmount();

Lazy mounting

When lazy: true is set, islands are not mounted immediately. Instead, each host element is observed with an IntersectionObserver and mounted only when it enters the viewport. This keeps the initial page load lean when islands are below the fold.

import { const ilha: IlhaFactoryilha, const mount: (registry: IslandRegistry, options?: MountOptions) => MountResultmount } from "ilha";

const const HeavyChart: Island<unknown>HeavyChart = ilha<unknown>(component: IslandComponent<unknown>): Island<unknown> (+3 overloads)ilha(() => "<canvas></canvas>");

function mount(registry: IslandRegistry, options?: MountOptions): MountResultmount({ type HeavyChart: Island<unknown>HeavyChart }, { MountOptions.lazy?: boolean | undefinedlazy: true });

Once an island becomes visible it mounts normally and is no longer observed.

Passing props

Props can be embedded directly in the HTML using data-ilha-props. mount() reads and parses this attribute automatically — no need to pass props through JavaScript:

<div data-ilha="Counter" data-ilha-props='{"start":10}'></div>
import { const ilha: IlhaFactoryilha, const mount: (registry: IslandRegistry, options?: MountOptions) => MountResultmount } from "ilha";

const const Counter: Island<unknown>Counter = ilha<unknown>(component: IslandComponent<unknown>): Island<unknown> (+3 overloads)ilha(() => "<p>counter</p>");

// No props needed here — they are read from data-ilha-props
function mount(registry: IslandRegistry, options?: MountOptions): MountResultmount({ type Counter: Island<unknown>Counter });

Hydration with state snapshots

When using .hydratable() on the server, the rendered HTML includes a data-ilha-state attribute with a snapshot of signal values. mount() reads this automatically and restores state without re-fetching or re-computing:

<div data-ilha="Counter" data-ilha-state='{"count":42}'></div>
import { const ilha: IlhaFactoryilha, const mount: (registry: IslandRegistry, options?: MountOptions) => MountResultmount } from "ilha";

const const Counter: Island<unknown>Counter = ilha<unknown>(component: IslandComponent<unknown>): Island<unknown> (+3 overloads)ilha(() => "<p>counter</p>");

// Reads data-ilha-state and restores signals from snapshot
function mount(registry: IslandRegistry, options?: MountOptions): MountResultmount({ type Counter: Island<unknown>Counter });

See Render and hydrate for how to generate this output on the server.

Scoping to a subtree

Pass a root element to limit discovery to a specific part of the page. This is useful when islands are injected dynamically into a container:

import { const ilha: IlhaFactoryilha, const mount: (registry: IslandRegistry, options?: MountOptions) => MountResultmount } from "ilha";

const const Widget: Island<unknown>Widget = ilha<unknown>(component: IslandComponent<unknown>): Island<unknown> (+3 overloads)ilha(() => "<p>widget</p>");

const const container: HTMLElementcontainer = var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.getElementById(elementId: string): HTMLElement | null
The **`getElementById()`** method of the Document interface returns an Element object representing the element whose id property matches the specified string. Since element IDs are required to be unique if specified, they're a useful way to get access to a specific element quickly.
getElementById
("dynamic-content")!;
const { const unmount: () => void | Promise<void>unmount } = function mount(registry: IslandRegistry, options?: MountOptions): MountResultmount({ type Widget: Island<unknown>Widget }, { MountOptions.root?: Element | undefinedroot: const container: HTMLElementcontainer });

Notes

  • If a data-ilha value has no matching key in the registry, that element is silently skipped.
  • In dev mode, double-mounting the same element logs a warning and returns a no-op for that element.
  • mount() is safe to call before the DOM is fully loaded if you wrap it in a DOMContentLoaded listener or place the script at the end of <body>.

Build HTML strings

An XSS-safe tagged template for building HTML strings. This is ilha’s low-level templating API: you can use it directly, but JSX is the preferred authoring style for most apps.

Because html`` is plain TypeScript/JavaScript, it does not require JSX syntax, a JSX runtime import, or a build transform. That makes it a great fit for no-build apps, small scripts, server-only rendering, or any place where you want ilha’s escaping and composition rules without JSX tooling.

Interpolated values are HTML-escaped by default, making the safe path the default and explicit opt-in required for raw markup.

Basic usage

import { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml } from "ilha";

const const name: "<script>alert(1)</script>"name = "<script>alert(1)</script>";

const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml`<p>${const name: "<script>alert(1)</script>"name}</p>`;
// → <p>&lt;script&gt;alert(1)&lt;/script&gt;</p>

Interpolation rules

Value type Behavior
string / number HTML-escaped
null / undefined Omitted — renders as empty string
raw(str) Inserted as-is, no escaping
html\…`` Inserted as-is, already safe
Signal accessor Called automatically, value is escaped
Array Each item processed recursively, no commas

Escaping

All string and number interpolations are escaped automatically:

import { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml } from "ilha";

const const userInput: "<img src=x onerror=\"alert(1)\">"userInput = `<img src=x onerror="alert(1)">`;
const const count: 42count = 42;

const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml`<p>${const userInput: "<img src=x onerror=\"alert(1)\">"userInput}</p>`; // → <p>&lt;img src=x…&gt;</p>
const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml`<p>${const count: 42count}</p>`; // → <p>42</p>

The characters &, <, >, ", and ' are all escaped.

Skipping null and undefined

null and undefined are silently omitted, making conditional rendering clean:

import { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml } from "ilha";

const const error: nullerror = null;

const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml`<div>${const error: nullerror}</div>`;
// → <div></div>

Trusted markup with raw()

When you need to inject pre-sanitized or server-controlled markup, use raw() to opt out of escaping:

import { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml, const raw: (value: string) => RawHtmlraw } from "ilha";

const const icon: "<svg aria-hidden=\"true\">…</svg>"icon = `<svg aria-hidden="true">…</svg>`;

const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml`<button>${function raw(value: string): RawHtmlraw(const icon: "<svg aria-hidden=\"true\">…</svg>"icon)} Submit</button>`;
// → <button><svg aria-hidden="true">…</svg> Submit</button>

Only use raw() with markup you fully control. Never pass user input to raw().

Nesting html results

Results of html are already safe and pass through unescaped when interpolated into a parent template. This is the foundation of composable templates:

import { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml } from "ilha";

const const badge: RawHtmlbadge = const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml`<span class="badge">New</span>`;

const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml`<div class="card">
  ${const badge: RawHtmlbadge}
  <p>Content</p>
</div>`;
// → <div class="card"><span class="badge">New</span><p>Content</p></div>

Signal accessors

Signal accessors can be interpolated without calling them. ilha detects signal accessors and calls them automatically, then escapes the result:

import { const ilha: IlhaFactoryilha, const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml, function state<T>(init?: T | (() => T)): StateAccessor<T>
Declare island-local reactive state at this call position. The initializer applies only when the instance is created — later renders reuse the same underlying signal, so prop-driven initializers never reset user state. A function argument is treated as a lazy initializer: const count = state(() => expensiveInitialValue()); To store a function VALUE, return it from the updater wrapper on write: setCallback(() => nextCallback);
state
} from "ilha";
const const Island: Island<unknown>Island = ilha<unknown>(component: IslandComponent<unknown>): Island<unknown> (+3 overloads)ilha(() => { const const label: StateAccessor<string>label = state<string>(init?: string | (() => string) | undefined): StateAccessor<string>
Declare island-local reactive state at this call position. The initializer applies only when the instance is created — later renders reuse the same underlying signal, so prop-driven initializers never reset user state. A function argument is treated as a lazy initializer: const count = state(() => expensiveInitialValue()); To store a function VALUE, return it from the updater wrapper on write: setCallback(() => nextCallback);
state
("<b>hello</b>");
return const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml` <p>${const label: StateAccessor<string>label}</p> `; });

Both forms are equivalent. The no-call shorthand is purely a convenience.

URL attributes

Full-value URL attributes get the same scheme filter as JSX. If the interpolated value is the entire attribute value, an unsafe scheme (javascript:, vbscript:, script-capable data: subtypes such as data:text/html or data:image/svg+xml) drops the attribute:

import { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml } from "ilha";

const const url: "javascript:alert(1)"url = "javascript:alert(1)";

const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml`<a href=${const url: "javascript:alert(1)"url}>x</a>`;
// → <a>x</a>   (attribute dropped, like JSX)

Safe targets pass through unchanged: https:/http: URLs, relative paths, mailto:, and image data: URIs like data:image/png;base64,…. srcdoc is dropped outright, matching JSX — attribute escaping cannot neutralize it because the browser re-decodes entities into live markup.

raw() in a URL attribute passes through unescaped, as in JSX — it is the author-owned escape hatch for values the filter rejects (for example a vetted SVG data URI). This only works when the interpolation is the whole value; partial values (href="/u/${id}") are plain escaped interpolation and cannot be scheme-checked.

Unquoted attribute values

An unquoted attribute whose value contains a character the HTML tokenizer treats as a terminator (whitespace, =, backtick) is re-emitted quoted and escaped. What used to be an attribute-injection hole is now one parsed attribute:

import { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml } from "ilha";

const const value: "x onmouseover=alert(1) y"value = "x onmouseover=alert(1) y";

const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml`<a href=${const value: "x onmouseover=alert(1) y"value}>x</a>`;
// → <a href="x onmouseover=alert(1) y">x</a>  (no onmouseover attribute)

Benign unquoted values keep the author’s literal form (<a href=/path> stays <a href=/path>).

Script and style data

HTML escaping is the wrong encoding inside <script> and <style> content: entities are not decoded by the JS/CSS tokenizer, and a stray </script> or </style> truncates the element. Use the json() and css() helpers for executable-element data — they escape < so the value can never close the block or open the <!-- escape hatch:

import { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml, function css(value: string): RawHtml
Escape text for a `<style>` element body. `<` becomes the CSS hex escape `\3C `, so the value can never close the style block or open the `<!--` escape hatch while surviving CSS parsing: ```ts html`<style>${css(cssSource)}</style>` ``` Like `raw()`, `css()` is for element *content* only — interpolating it into an attribute is author error.
css
, function json(value: unknown): RawHtml
Serialize a JSON-safe value into executable `<script>` content safely. `<` is escaped as `\u003C`, so a value can never close the script block or open the `<!--` escape hatch. Use it for script-element data: ```ts html`<script>const d = ${json(payload)};</script>` ``` Like `raw()`, `json()` is for element *content* only — interpolating it into an attribute is author error. It does not protect against values that are not valid JSON (functions, symbols, undefined, cyclic objects are dropped or throw, matching `JSON.stringify`).
json
} from "ilha";
const
const payload: {
    name: string;
}
payload
= { name: stringname: "</script><img src=x>" };
const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml`<script> const d = ${function json(value: unknown): RawHtml
Serialize a JSON-safe value into executable `<script>` content safely. `<` is escaped as `\u003C`, so a value can never close the script block or open the `<!--` escape hatch. Use it for script-element data: ```ts html`<script>const d = ${json(payload)};</script>` ``` Like `raw()`, `json()` is for element *content* only — interpolating it into an attribute is author error. It does not protect against values that are not valid JSON (functions, symbols, undefined, cyclic objects are dropped or throw, matching `JSON.stringify`).
json
(
const payload: {
    name: string;
}
payload
)};
</script>`; // → <script>const d = {"name":"\u003C/script>\u003Cimg src=x"};</script> const const cssSource: "a{content:'</style>'}"cssSource = "a{content:'</style>'}"; const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml`<style> ${function css(value: string): RawHtml
Escape text for a `<style>` element body. `<` becomes the CSS hex escape `\3C `, so the value can never close the style block or open the `<!--` escape hatch while surviving CSS parsing: ```ts html`<style>${css(cssSource)}</style>` ``` Like `raw()`, `css()` is for element *content* only — interpolating it into an attribute is author error.
css
(const cssSource: "a{content:'</style>'}"cssSource)}
</style>`; // → <style>a{content:'\3C /style>'}</style>

json() takes any JSON.stringify-able value and escapes < as \u003C; the JS engine decodes it inside string literals, so data round-trips exactly. css() takes a string and escapes < as the CSS hex escape \3C. Like raw(), both are for element content only — interpolating them into an attribute is author error.

Native event handlers

Use lowercase on* attributes with function interpolations inside an island render:

import { const ilha: IlhaFactoryilha, const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml, function state<T>(init?: T | (() => T)): StateAccessor<T>
Declare island-local reactive state at this call position. The initializer applies only when the instance is created — later renders reuse the same underlying signal, so prop-driven initializers never reset user state. A function argument is treated as a lazy initializer: const count = state(() => expensiveInitialValue()); To store a function VALUE, return it from the updater wrapper on write: setCallback(() => nextCallback);
state
} from "ilha";
const const Form: Island<unknown>Form = ilha<unknown>(component: IslandComponent<unknown>): Island<unknown> (+3 overloads)ilha(() => { const const name: StateAccessor<string>name = state<string>(init?: string | (() => string) | undefined): StateAccessor<string>
Declare island-local reactive state at this call position. The initializer applies only when the instance is created — later renders reuse the same underlying signal, so prop-driven initializers never reset user state. A function argument is treated as a lazy initializer: const count = state(() => expensiveInitialValue()); To store a function VALUE, return it from the updater wrapper on write: setCallback(() => nextCallback);
state
("");
return const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml` <form onsubmit=${(event: SubmitEventevent: SubmitEvent) => { event: SubmitEventevent.Event.preventDefault(): void
The **`preventDefault()`** method of the Event interface tells the user agent that the event is being explicitly handled, so its default action, such as page scrolling, link navigation, or pasting text, should not be taken. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/preventDefault)
preventDefault
();
var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
(
const name: MarkedSignalAccessor
() => string (+1 overload)
name
());
}} > <input bind:value=${const name: StateAccessor<string>name} onselect=${(event: Eventevent: Event) => var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
(event: Eventevent.Event.currentTarget: EventTarget | null
The **`currentTarget`** read-only property of the Event interface identifies the element to which the event handler has been attached. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget)
currentTarget
)}
/> <button type="submit">Save</button> </form> `; });

The same syntax works with onclick, oninput, onchange, onreset, and other native event names. The handler receives the native DOM event and a second context argument containing a lifecycle signal.

Add one listener modifier after the event name:

import { const ilha: IlhaFactoryilha, const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml, type NativeEventContext } from "ilha";

const const Search: Island<unknown>Search = ilha<unknown>(component: IslandComponent<unknown>): Island<unknown> (+3 overloads)ilha(
  () => const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml`
    <input
      oninput:abortable=${async (
        event: InputEventevent: InputEvent,
        { signal: AbortSignalsignal }: NativeEventContext,
      ) => {
        const const input: HTMLInputElementinput = event: InputEventevent.Event.currentTarget: EventTarget | null
The **`currentTarget`** read-only property of the Event interface identifies the element to which the event handler has been attached. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget)
currentTarget
as HTMLInputElement;
await function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)
fetch
(`/api/search?q=${const input: HTMLInputElementinput.HTMLInputElement.value: string
The **`value`** property of the HTMLInputElement interface represents the current value of the <input> element as a string. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/value)
value
}`, { RequestInit.signal?: AbortSignal | null | undefined
An AbortSignal to set request's signal.
signal
});
}} /> `, );

Use :once, :capture, :passive, or :abortable. The :abortable modifier aborts the previous invocation when the same event fires again. Native handlers accept one modifier.

Event handlers work only while an island renders and mounts the template. Ilha refreshes closures after re-renders and aborts the handler signal when it replaces a listener or starts unmounting the island. A standalone html`` result has no host or lifecycle, so Ilha omits its event function.

Functions never become inline HTML attributes during SSR. Ilha emits inert markup and attaches each function with addEventListener during mount.

Keep the function as an event-attribute interpolation. You can compose an attribute-only nested template when the attribute is conditional:

import { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml } from "ilha";

const const enabled: trueenabled = true;
const const handler: () => voidhandler = () => {};
const const events: RawHtml | ""events = const enabled: trueenabled ? const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml` onclick=${const handler: () => voidhandler}` : "";
const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml`<button class="save" ${const events: RawHtml | ""events}>Save</button>`;

Ilha cannot register handlers hidden inside serialized attribute objects or raw() strings. Those paths produce plain markup. For host-level listeners or delegated selectors, attach them with effect.once() using native DOM APIs.

Use bind:* for signal synchronization. Use effect.once() to attach host listeners with native DOM APIs when handlers do not sit on one rendered element.

List rendering

Arrays are processed recursively with no comma joining. The canonical list pattern is:

import { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml } from "ilha";

const const fruits: string[]fruits = ["apple", "banana", "cherry"];

const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml`
  <ul>
    ${const fruits: string[]fruits.Array<string>.map<RawHtml>(callbackfn: (value: string, index: number, array: string[]) => RawHtml, thisArg?: any): RawHtml[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
((fruit: stringfruit) => const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml`<li>${fruit: stringfruit}</li>`)}
</ul> `; // → <ul><li>apple</li><li>banana</li><li>cherry</li></ul>

Each html result in the array passes through unescaped. Mixed arrays of strings and html results also work — each item is processed by its own rules.

Whitespace and indentation

html\`` automatically strips leading and trailing blank lines and dedents the template based on the minimum indentation found. This keeps rendered output clean regardless of how the template is indented in source:

import { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml } from "ilha";

const const result: RawHtmlresult = const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml`
  <div>
    <p>Hello</p>
  </div>
`;
// → <div>\n  <p>Hello</p>\n</div>

Return type

html returns a RawHtml object, not a plain string. This lets ilha distinguish between trusted and untrusted content when the result is interpolated into another template. To get the plain string value, access .value or let ilha unwrap it at a render boundary:

import { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml } from "ilha";

const const result: RawHtmlresult = const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtmlhtml`<p>hello</p>`;

const result: RawHtmlresult.RawHtml.value: stringvalue; // → "<p>hello</p>"

In practice you rarely need to access .value directly — ilha handles unwrapping automatically at render time.

Notes

  • html is purely a runtime helper with no compiler step. It works in any JavaScript environment including Node, Bun, Deno, and the browser.
  • Full-value attribute interpolation (href=${…} / href="${…}") applies the same URL/style policy as JSX. Values assembled from several interpolations (href="/u/${a}/${b}") stay plain escaped interpolation — scheme-checking cannot span partial values, so keep user input out of URL pieces.
  • Style objects (style=${{ color: "red" }}) serialize through the same allowlisted property list as JSX.
  • For stylesheets and script bodies, use json() / css() (see above) rather than raw string interpolation.

Render trusted markup

Marks a string as trusted HTML, bypassing escaping when rendered in JSX or interpolated inside html. Use it when you need to inject markup you fully control — icons, pre-rendered fragments, or server-sanitized content.

Basic usage

import { const raw: (value: string) => RawHtmlraw } from "ilha";

<"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>{function raw(value: string): RawHtmlraw("<em>hello</em>")}</"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>;
// → <div><em>hello</em></div>

Without raw(), the same string would be escaped:

<"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>{"<em>hello</em>"}</"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>
// → <div>&lt;em&gt;hello&lt;/em&gt;</div>

When to use it

raw() is appropriate when the markup comes from a source you fully control:

import { const ilha: IlhaFactoryilha, const raw: (value: string) => RawHtmlraw } from "ilha";

// SVG icons defined in your codebase
const const chevron: "<svg viewBox=\"0 0 16 16\">\n    <path d=\"M4 6l4 4 4-4\"/>\n</svg>"chevron = `<svg viewBox="0 0 16 16">
    <path d="M4 6l4 4 4-4"/>
</svg>`;

const const Dropdown: Island<unknown>Dropdown = ilha<unknown>(component: IslandComponent<unknown>): Island<unknown> (+3 overloads)ilha(() => (
  <
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
>Options {function raw(value: string): RawHtmlraw(const chevron: "<svg viewBox=\"0 0 16 16\">\n <path d=\"M4 6l4 4 4-4\"/>\n</svg>"chevron)}</
"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
    popovertarget?: string;
    popoverTarget?: string;
    popovertargetaction?: "hide" | "show" | "toggle";
    popoverTargetAction?: "hide" | "show" | "toggle";
}>
button
>
));
import { const raw: (value: string) => RawHtmlraw } from "ilha";

// Pre-rendered HTML from a trusted server-side renderer
const const renderedMarkdown: "<h1>Title</h1><p>Body text.</p>"renderedMarkdown = `<h1>Title</h1><p>Body text.</p>`;

<"article": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLElement>>article>{function raw(value: string): RawHtmlraw(const renderedMarkdown: "<h1>Title</h1><p>Body text.</p>"renderedMarkdown)}</"article": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLElement>>article>;

When not to use it

Never pass user input to raw(). It disables all escaping, so any unescaped string becomes a potential XSS vector:

import { const raw: (value: string) => RawHtmlraw } from "ilha";

// ❌ Never do this
const const userComment: "<script>alert(1)</script>"userComment = `<script>alert(1)</script>`;
<"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{function raw(value: string): RawHtmlraw(const userComment: "<script>alert(1)</script>"userComment)}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>;

// ✅ Do this instead — JSX escapes it automatically
<"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{const userComment: "<script>alert(1)</script>"userComment}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>;

Composing with JSX

JSX results are already treated as safe and pass through unescaped without needing raw(). Reserve raw() for plain strings that contain trusted markup:

import { const raw: (value: string) => RawHtmlraw } from "ilha";

// JSX result — no raw() needed
const const badge: JSX.Elementbadge = <"span": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLSpanElement>>span class?: RawHtml | ClassValue | undefinedclass="badge">New</"span": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLSpanElement>>span>;
<"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>{const badge: JSX.Elementbadge}</"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>;

// Plain string with markup — raw() required
const const iconStr: "<svg>…</svg>"iconStr = `<svg>…</svg>`;
<"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>{function raw(value: string): RawHtmlraw(const iconStr: "<svg>…</svg>"iconStr)}</"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>;

Return type

raw() returns a RawHtml object. This means raw values compose freely with JSX and arrays:

import { const raw: (value: string) => RawHtmlraw } from "ilha";

const const icons: string[]icons = ["<svg>…</svg>", "<svg>…</svg>"];

<"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>ul>
  {const icons: string[]icons.Array<string>.map<JSX.Element>(callbackfn: (value: string, index: number, array: string[]) => JSX.Element, thisArg?: any): JSX.Element[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
((icon: stringicon) => (
<
"li": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLLIElement> & {
    value?: number;
}>
li
>{function raw(value: string): RawHtmlraw(icon: stringicon)}</
"li": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLLIElement> & {
    value?: number;
}>
li
>
))} </"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>ul>;

Notes

  • raw() only has an effect when rendered by ilha JSX or html. Elsewhere it simply wraps the string in a RawHtml object with no other transformation.
  • There is no runtime sanitization inside raw(). If you need to accept user-generated HTML, sanitize it with a dedicated library such as DOMPurify before passing it to raw().

Was this page helpful?