Using the Safe Attribute

Estimated reading time: 3 minutes

Every child expression in Kita Html renders without escaping by default. To escape user input, apply the safe attribute to the nearest native element wrapping the expression.

Native elements

Add safe to the element that directly contains the dynamic content. The runtime passes all children through HTML entity escaping before concatenation.

const 
const html: JSX.Element
html
= <
JSX.IntrinsicElements.div: JSX.HtmlTag
div
JSX.HtmlTag.safe?: boolean | undefined

When set to true, all inner content (html or not) of this tag will be escaped when evaluated.

Warning: This will escape even inner jsx tags. You should only use this in the most inner tag of the html tree.

@example
<div>{'<script />'}</div>
'<div><script /></div>'
<div safe>{'<script />'}</div>
'<div><script /></div>'
<div><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'

// Escapes even inner jsx tags
<div safe><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'
@defaultfalse@seehttps ://github.com/kitajs/html/tree/master/packages/html#sanitization
safe
>{
let userInput: "<script>alert('XSS')</script>"
userInput
}</
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>
<div>&lt;script>alert(&#39;XSS&#39;)&lt;/script></div>

Without safe, malicious input executes. Consider a user profile where the description field contains </div><script>fetch('/steal', {method: 'POST', body: document.cookie})</script>. Rendering this without escaping closes the container early, injects a script tag, and runs arbitrary code. The safe attribute converts the angle brackets to &lt; and &gt;, rendering the payload as harmless text.

Place safe on the innermost element that holds the untrusted value. Do not add it to a parent wrapper, as that would escape the HTML of child components too.

function 
function UserCard({ name, bio }: {
    name: string;
    bio: string;
}): JSX.Element
UserCard
({
name: string
name
,
bio: string
bio
}: {
name: string
name
: string;
bio: string
bio
: string }) {
return ( <
JSX.IntrinsicElements.div: JSX.HtmlTag
div
JSX.HtmlTag.class?: string | (string | number | boolean | null | undefined)[] | undefined

The html class property. You can use an array to represent conditional class names. Similar to the clsx package behavior.

@example
<div class={['a', true && 'b', false && 'c', 'd']} />
'<div class="a b d"></div>'

<div class={['class-a class-b', true && 'class-c']} />
'<div class="class-a class-b class-c"></div>'
class
="card">
<
JSX.IntrinsicElements.h2: JSX.HtmlTag
h2
JSX.HtmlTag.safe?: boolean | undefined

When set to true, all inner content (html or not) of this tag will be escaped when evaluated.

Warning: This will escape even inner jsx tags. You should only use this in the most inner tag of the html tree.

@example
<div>{'<script />'}</div>
'<div><script /></div>'
<div safe>{'<script />'}</div>
'<div><script /></div>'
<div><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'

// Escapes even inner jsx tags
<div safe><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'
@defaultfalse@seehttps ://github.com/kitajs/html/tree/master/packages/html#sanitization
safe
>{
name: string
name
}</
JSX.IntrinsicElements.h2: JSX.HtmlTag
h2
>
<
JSX.IntrinsicElements.p: JSX.HtmlTag
p
JSX.HtmlTag.safe?: boolean | undefined

When set to true, all inner content (html or not) of this tag will be escaped when evaluated.

Warning: This will escape even inner jsx tags. You should only use this in the most inner tag of the html tree.

@example
<div>{'<script />'}</div>
'<div><script /></div>'
<div safe>{'<script />'}</div>
'<div><script /></div>'
<div><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'

// Escapes even inner jsx tags
<div safe><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'
@defaultfalse@seehttps ://github.com/kitajs/html/tree/master/packages/html#sanitization
safe
>{
bio: string
bio
}</
JSX.IntrinsicElements.p: JSX.HtmlTag
p
>
</
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>
) }
<div class="card">
  <h2>Name</h2>
  <p>Bio</p>
</div>

Component children

The safe attribute only applies to native elements. If you pass an unsafe value as a child to a component, you must either escape it manually or wrap it in a Fragment with the safe attribute to tell the plugin it's already escaped. This prevents components from accidentally rendering unescaped HTML when they receive dynamic content as children.

let html: JSX.Element
html
= (
<
function MyComponent({ children }: {
    children: Children;
}): JSX.Element
MyComponent
>
<
function Fragment(props: PropsWithChildren<Pick<JSX.HtmlTag, "safe">>): JSX.Element

A JSX Fragment is used to return multiple elements from a component.

@example
// renders <div>1</div> and <div>2</div> without needing a wrapper element
const html = <><div>1</div><div>2</div></>

// Html.Fragment is the same as <>...</>
const html = <Html.Fragment><div>1</div><div>2</div></Html.Fragment>
Fragment
safe?: boolean | undefined

When set to true, all inner content (html or not) of this tag will be escaped when evaluated.

Warning: This will escape even inner jsx tags. You should only use this in the most inner tag of the html tree.

@example
<div>{'<script />'}</div>
'<div><script /></div>'
<div safe>{'<script />'}</div>
'<div><script /></div>'
<div><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'

// Escapes even inner jsx tags
<div safe><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'
@defaultfalse@seehttps ://github.com/kitajs/html/tree/master/packages/html#sanitization
safe
>{
let userInput: string
userInput
}</
function Fragment(props: PropsWithChildren<Pick<JSX.HtmlTag, "safe">>): JSX.Element

A JSX Fragment is used to return multiple elements from a component.

@example
// renders <div>1</div> and <div>2</div> without needing a wrapper element
const html = <><div>1</div><div>2</div></>

// Html.Fragment is the same as <>...</>
const html = <Html.Fragment><div>1</div><div>2</div></Html.Fragment>
Fragment
>
</
function MyComponent({ children }: {
    children: Children;
}): JSX.Element
MyComponent
>
) // Or escape explicitly when the component doesn't support safe
let html: JSX.Element
html
= <
function MyComponent({ children }: {
    children: Children;
}): JSX.Element
MyComponent
>{
function escapeHtml(this: void, value: any): string

Escapes a string for safe use as HTML text content. If the value is not a string, it is coerced to one with its own toString() method.

If the {@linkcode Bun } runtime is available, this function will be swapped out to

{@linkcode Bun.escapeHTML } .

@paramvalue The value to escape.@returnsThe escaped string.
escapeHtml
(
let userInput: string
userInput
)}</
function MyComponent({ children }: {
    children: Children;
}): JSX.Element
MyComponent
>
<div>User input!</div>

Template literal helper

The e tagged template is an alias to escapeHtml() that you can use as interpolated content in template literals. It provides a convenient way to escape dynamic values outside of JSX.

import { 
const e: (this: void, strings: TemplateStringsArray, ...values: any[]) => string

Alias of {@linkcode escape } to reduce verbosity.

@example
import { e } from '@kitajs/html'

<div>{e`My name is ${user.name}!`}</div>;
e
} from '@kitajs/html'
const
const html: string
html
=
const e: (this: void, strings: TemplateStringsArray, ...values: any[]) => string

Alias of {@linkcode escape } to reduce verbosity.

@example
import { e } from '@kitajs/html'

<div>{e`My name is ${user.name}!`}</div>;
e
`Hello, ${
const userName: "<script>untrusted()</script>"
userName
}!`
Hello, &lt;script>untrusted()&lt;/script>!

Suppression conventions

When the XSS detection plugin flags a value that you know is safe, you can suppress the warning without adding safe to the element, which would escape all children and potentially break components.

Danger

Suppressing warnings is dangerous because it allows unescaped HTML to slip through. Only use these techniques when you have a guarantee of safety.

Prefix the variable name with safe. The plugin treats any identifier starting with safe as pre-escaped.

const 
const safeContent: string
safeContent
=
function sanitizeElsewhere(raw: string): string
sanitizeElsewhere
(
const rawInput: "<script>alert('XSS')</script>"
rawInput
)
const
const html: JSX.Element
html
= <
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>{
const safeContent: string
safeContent
}</
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>
<div>&lt;script&gt;alert('XSS')&lt;/script&gt;</div>

Following the same logic, prefixing a variable with unsafe explicitly marks it as unescaped and triggers a warning if used in JSX.

const 
const unsafeContent: "My safe string"
unsafeContent
= 'My safe string' as
type const = "My safe string"
const
const
const html: JSX.Element
html
= <
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>{
const unsafeContent: "My safe string"
unsafeContent
}</
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>
<div>My safe string</div>
Warning

Manual casts and naming conventions are not enforced by the compiler. They rely on developer discipline and code reviews to ensure safety. Use them judiciously and document the rationale for any exceptions.

Cast the expression to 'safe' inline. This tells the plugin to skip the check for that specific usage and not warn about possible XSS vulnerabilities.

const 
const html: JSX.Element
html
= <
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>{
const content: string
content
as 'safe'}</
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>
<div>
  <script>
    untrusted();
  </script>
</div>

Call Html.escapeHtml() directly. The plugin recognizes the return value as escaped.

import { 
const Html: Omit<typeof import("@kitajs/html"), "Html" | "default">

Namespace export containing all core runtime functions. Used with the legacy jsx: "react" transform where jsxFactory is set to Html.createElement. When using the modern jsx: "react-jsx" transform, import individual functions directly from @kitajs/html instead.

@deprecatedThis namespace export is only provided for interop with the legacy jsx: "react".@seehttps ://github.com/kitajs/html@seehttps ://www.npmjs.com/package/@kitajs/html
Html
} from '@kitajs/html'
const
const html: JSX.Element
html
= <
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>{
const Html: Omit<typeof import("@kitajs/html"), "Html" | "default">

Namespace export containing all core runtime functions. Used with the legacy jsx: "react" transform where jsxFactory is set to Html.createElement. When using the modern jsx: "react-jsx" transform, import individual functions directly from @kitajs/html instead.

@deprecatedThis namespace export is only provided for interop with the legacy jsx: "react".@seehttps ://github.com/kitajs/html@seehttps ://www.npmjs.com/package/@kitajs/html
Html
.
escapeHtml: (this: void, value: any) => string

Escapes a string for safe use as HTML text content. If the value is not a string, it is coerced to one with its own toString() method.

If the {@linkcode Bun } runtime is available, this function will be swapped out to

{@linkcode Bun.escapeHTML } .

@paramvalue The value to escape.@returnsThe escaped string.
escapeHtml
(
const content: string
content
)}</
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>
<div>&lt;script>untrusted()&lt;/script></div>