html

Functions for HTML, such as escaping or unescaping HTML entities


[![typescript-image][typescript-badge]][typescript-url] [![mit licence][license-badge]][license] [![npm downloads][npm-downloads-badge]][npm-downloads] [![Chat][chat-badge]][chat] [![PRs Welcome][prs-welcome-badge]][prs-welcome]
---

Daniel Bannert's open source work is supported by the community on GitHub Sponsors

--- ## Features ### HTML Escaping - **Fast HTML Escaping**: Optimized HTML escaping function from Svelte - **Minimal Allocations**: Efficient string escaping with minimal memory allocations - **Dual Mode**: Supports both content escaping and attribute escaping - **XSS Protection**: Escapes HTML special characters to prevent XSS attacks - **HTML Template Tag**: Template literal function for HTML strings with automatic escaping of interpolated values - **TypeScript Support**: Full TypeScript definitions included ### CSS & JavaScript Escaping - **CSS Escaping**: Escape strings for safe interpolation into CSS stylesheets, `'); }"; const safeCss = escapeCss(unsafeCss); // Result: Escaped CSS that prevents injection attacks // Use in style elements const html = ``; // Use in inline styles const inlineStyle = `background-image: url('${escapeCss(userUrl)}');`; ``` #### CSS Selector Escaping ```typescript import { escapeCss } from "@visulima/html"; // Escape CSS selector values const selector = escapeCss(userInput); const css = `.${selector} { color: red; }`; ``` > **Security Note:** Always use `escapeCss` when interpolating user-generated content into CSS contexts to prevent CSS injection attacks. ### CSS Template Tag The `css` function provides a convenient template tag for creating CSS strings, with support for CSS objects and optional escaping. #### Template Tag Usage ```typescript import { css } from "@visulima/html"; // Template tag returns CSS as-is const styles = css` :where(.UnderlineNav-actions ul) { animation: 1ms rgh-selector-observer; } `; // Result: CSS string with preserved formatting // With template values const selector = ".test-class"; const color = "red"; const result = css` ${selector} { color: ${color}; } `; ``` > **Note:** The template tag collapses whitespace into single spaces to produce a one-line string, but it is quote-aware: whitespace inside single- or double-quoted values (e.g. `content: "a b"`) is preserved verbatim. #### Function Usage with String Input ```typescript import { css } from "@visulima/html"; // Return CSS as-is (no escaping) const cssString = css(":where(.test) { color: red; }", false); // Result: ':where(.test) { color: red; }' // Escape CSS for safe interpolation const escapedCss = css(":where(.test) { color: red; }", true); // Result: Escaped CSS string // Default behavior: return as-is const defaultCss = css(":where(.test) { color: red; }"); // Result: ':where(.test) { color: red; }' ``` #### Function Usage with CSS Object The `css` function accepts CSS objects with camelCase properties, providing full TypeScript autocomplete support via `csstype`: ```typescript import { css } from "@visulima/html"; // Convert CSS object to CSS string (no escaping) const styles = css({ padding: "1px", margin: "2px" }, false); // Result: 'padding: 1px; margin: 2px;' // With escaping const escapedStyles = css({ padding: "1px" }, true); // Result: Escaped CSS string // CamelCase properties are automatically converted to kebab-case const result = css({ paddingTop: "10px", marginBottom: "20px" }, false); // Result: 'padding-top: 10px; margin-bottom: 20px;' // Full TypeScript autocomplete for CSS properties const typedStyles = css( { padding: "1px", margin: "2px", color: "red", display: "block", // TypeScript will autocomplete all valid CSS properties! }, false, ); ``` #### TypeScript Autocomplete The `css` function uses `csstype` for full TypeScript autocomplete support: - **All CSS Properties**: Autocomplete for all standard CSS properties - **Type Safety**: TypeScript will validate CSS property names and values - **CamelCase Support**: Use camelCase property names (e.g., `paddingTop`) which are automatically converted to kebab-case #### Use Cases - **Template Literals**: Use the template tag for static CSS or CSS with template variables - **CSS Objects**: Use CSS objects when you need TypeScript autocomplete and type safety - **Dynamic Styles**: Convert JavaScript objects to CSS strings for dynamic styling - **Safe Interpolation**: Use `escape: true` when interpolating user-generated CSS ### JavaScript Escaping The `escapeJs` function escapes a JavaScript object or other data for safe interpolation inside a `"; const safeJs = escapeJs(unsafeJs); // Result: Escaped JavaScript that prevents script injection // Use in script tags const html = ``; // Escape JSON data for inline scripts const jsonData = { name: "John", value: "`; ``` #### Escaping JavaScript Objects ```typescript import { escapeJs } from "@visulima/html"; // Escape complex objects for safe interpolation const config = { apiUrl: "https://api.example.com", userInput: userProvidedValue, }; const escapedConfig = escapeJs(JSON.stringify(config)); const html = ``; ``` > **Security Note:** Always use `escapeJs` when interpolating user-generated content into JavaScript contexts to prevent XSS attacks and script injection. ### Custom Element Name Validation The `isValidCustomElementName` function checks whether a given string is a valid custom element name according to the [HTML specification](https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name). #### Basic Validation ```typescript import { isValidCustomElementName } from "@visulima/html"; // Valid custom element names (must contain a hyphen) console.log(isValidCustomElementName("my-element")); // true console.log(isValidCustomElementName("my-custom-element")); // true console.log(isValidCustomElementName("app-header")); // true // Invalid custom element names console.log(isValidCustomElementName("MyElement")); // false (no hyphen) console.log(isValidCustomElementName("my_element")); // false (underscore not allowed) console.log(isValidCustomElementName("myelement")); // false (no hyphen) console.log(isValidCustomElementName("div")); // false (standard HTML tag) ``` #### Using with Custom Element Registration ```typescript import { isValidCustomElementName } from "@visulima/html"; function registerCustomElement(name: string, constructor: CustomElementConstructor) { if (!isValidCustomElementName(name)) { throw new Error(`Invalid custom element name: ${name}. Custom element names must contain a hyphen.`); } customElements.define(name, constructor); } // Valid usage registerCustomElement("my-component", class extends HTMLElement {}); // Invalid usage - will throw error registerCustomElement("MyComponent", class extends HTMLElement {}); // Error! ``` #### Custom Element Name Rules According to the HTML specification, a valid custom element name must: - Contain a hyphen (`-`) to separate words - Start with an ASCII lowercase letter - Contain at least one hyphen - Not be a standard HTML tag name - Not start with certain reserved prefixes (like `html-`, `xml-`, etc.) > **Note:** Custom element names are case-sensitive and must follow the naming conventions defined in the HTML specification to ensure proper browser support. ### HTML Detection The `isHtml` function checks whether a string contains HTML markup. It is a thin re-export of [`is-html`](https://github.com/sindresorhus/is-html). ```typescript import { isHtml } from "@visulima/html"; isHtml("

hello

"); // true isHtml("
"); // true isHtml("just plain text"); // false isHtml("1 < 2 and 3 > 2"); // false ``` ### HTML Entity Encoding & Decoding The package exports all functions from `html-entities` for encoding and decoding HTML entities. #### Basic Encoding ```typescript import { encode } from "@visulima/html"; // Encode HTML special characters const encoded = encode("< > \" ' & © ∆"); // Result: '< > " ' & © ∆' ``` #### Basic Decoding ```typescript import { decode } from "@visulima/html"; // Decode HTML entities const decoded = decode("< > " ' & © Δ"); // Result: '< > " \' & © ∆' ``` #### Encoding Options ```typescript import { encode } from "@visulima/html"; // Encode with HTML5 standard (default) encode("< > \" ' & ©", { level: "html5" }); // Result: '< > " ' & ©' // Encode with HTML4 standard encode("< > \" ' & ©", { level: "html4" }); // Result: '< > " ' & ©' // Encode with XML standard encode("< > \" ' & ©", { level: "xml" }); // Result: '< > " ' & ©' // Encode only special characters (default mode) encode("< > \" ' & ©", { mode: "specialChars" }); // Result: '< > " ' & ©' // Encode HTML special characters and everything outside ASCII encode("< ©", { mode: "nonAscii" }); // Result: '< ©' // Encode HTML special characters and everything outside ASCII printable encode("< ©", { mode: "nonAsciiPrintable" }); // Result: '< ©' // Encode with XML level and non-ASCII printable mode encode("< ©", { mode: "nonAsciiPrintable", level: "xml" }); // Result: '< ©' // Encode only non-ASCII printable characters (keep HTML special chars intact) encode("< > \" ' & ©", { mode: "nonAsciiPrintableOnly", level: "xml" }); // Result: '< > " \' & ©' // Encode extensively (all non-printable, non-ASCII, and named references) encode("< > \" ' & ©", { mode: "extensive" }); // Result: '< > " ' & ©' // Use hexadecimal numeric entities encode("< ©", { mode: "nonAsciiPrintable", level: "xml", numeric: "hexadecimal" }); // Result: '< ©' ``` **Encode Options:** - `level`: `'all'` (alias to `'html5'`) | `'html5'` (default) | `'html4'` | `'xml'` - Specifies the standard to use for named character references - `mode`: `'specialChars'` (default) | `'nonAscii'` | `'nonAsciiPrintable'` | `'nonAsciiPrintableOnly'` | `'extensive'` - Determines which characters to encode - `numeric`: `'decimal'` (default) | `'hexadecimal'` - Uses decimal (`©`) or hexadecimal (`©`) numbers when encoding entities #### Decoding Options ```typescript import { decode } from "@visulima/html"; // Decode with HTML5 standard (default) decode("< > " ' & © ∆"); // Result: '< > " \' & © ∆' // Decode with HTML5 level decode("©", { level: "html5" }); // Result: '©' // Decode with XML level (doesn't recognize ©) decode("©", { level: "xml" }); // Result: '©' (unknown entity left as is) // Decode with body scope (default) - emulates browser parsing tag bodies decode("< >", { scope: "body" }); // Result: '< >' (entities without semicolon are replaced) // Decode with attribute scope - emulates browser parsing tag attributes decode("< >", { scope: "attribute" }); // Result: '< >' (entities without semicolon replaced when not followed by =) // Decode with strict scope - ignores entities without semicolon decode("< >", { scope: "strict" }); // Result: '< >' (entities without semicolon ignored) ``` **Decode Options:** - `level`: `'all'` (alias to `'html5'`) | `'html5'` (default) | `'html4'` | `'xml'` - Specifies the standard to use for named character references - `scope`: `'body'` (default) | `'attribute'` | `'strict'` - Controls how entities without semicolons are handled - `'body'`: Emulates browser behavior when parsing tag bodies - entities without semicolon are also replaced - `'attribute'`: Emulates browser behavior when parsing tag attributes - entities without semicolon are replaced when not followed by equality sign `=` - `'strict'`: Ignores entities without semicolon #### Decode Single Entity ```typescript import { decodeEntity } from "@visulima/html"; // Decode a single HTML entity decodeEntity("<"); // Result: '<' // Decode with HTML5 level decodeEntity("©", { level: "html5" }); // Result: '©' // Decode with XML level (doesn't recognize ©) decodeEntity("©", { level: "xml" }); // Result: '©' (unknown entity left as is) ``` **DecodeEntity Options:** - `level`: `'all'` (alias to `'html5'`) | `'html5'` (default) | `'html4'` | `'xml'` - Specifies the standard to use for named character references ### HTML Tag Lists The package exports `htmlTags` and `voidHtmlTags` from `html-tags` for working with HTML tag lists. #### Standard HTML Tags ```typescript import { htmlTags } from "@visulima/html"; // Get all standard HTML tags console.log(htmlTags); // => ['a', 'abbr', 'acronym', 'address', 'applet', 'area', 'article', ...] // Check if a tag is a standard HTML tag const isValidTag = htmlTags.includes("div"); // => true const isInvalidTag = htmlTags.includes("custom-tag"); // => false // Use with sanitizeHtml to validate allowed tags import { sanitizeHtml, htmlTags } from "@visulima/html"; const clean = sanitizeHtml(dirtyHtml, { allowedTags: htmlTags.filter((tag) => ["p", "a", "img", "div"].includes(tag)), }); ``` #### Void HTML Tags ```typescript import { voidHtmlTags } from "@visulima/html"; // Get all void/self-closing HTML tags console.log(voidHtmlTags); // => ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', ...] // Check if a tag is a void tag const isVoidTag = voidHtmlTags.includes("br"); // => true const isNotVoidTag = voidHtmlTags.includes("div"); // => false // Use with sanitizeHtml to configure self-closing tags import { sanitizeHtml, voidHtmlTags } from "@visulima/html"; const clean = sanitizeHtml(html, { allowedTags: ["p", "br", "img"], selfClosing: voidHtmlTags.filter((tag) => ["br", "img"].includes(tag)), }); ``` ### HTML Sanitization The package exports `sanitizeHtml` from `sanitize-html` for cleaning user-submitted HTML. #### Basic Sanitization ```typescript import { sanitizeHtml } from "@visulima/html"; // Basic usage - removes potentially dangerous HTML const dirty = '

Hello World

'; const clean = sanitizeHtml(dirty); // Result: '

Hello World

' ``` #### Custom Allowed Tags and Attributes ```typescript import { sanitizeHtml } from "@visulima/html"; // Specify allowed tags and attributes const html = '

Hello Link

'; const clean = sanitizeHtml(html, { allowedTags: ["b", "i", "em", "strong", "a", "p"], allowedAttributes: { a: ["href"], }, }); ``` #### Extending Default Allowed Tags ```typescript import { sanitizeHtml } from "@visulima/html"; // Extend the default set of allowed tags const clean = sanitizeHtml(html, { allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img", "iframe"]), }); ``` #### Advanced Sanitization Options ```typescript import { sanitizeHtml } from "@visulima/html"; const clean = sanitizeHtml(html, { // Allowed HTML tags allowedTags: ["h1", "h2", "p", "a", "img"], // Allowed attributes per tag allowedAttributes: { a: ["href", "name", "target"], img: ["src", "alt", "width", "height"], }, // Self-closing tags selfClosing: ["img", "br", "hr"], // Allowed URL schemes allowedSchemes: ["http", "https", "mailto"], // Allowed schemes for specific tags allowedSchemesByTag: { img: ["http", "https", "data"], }, // Attributes that scheme validation applies to allowedSchemesAppliedToAttributes: ["href", "src", "cite"], // Allow protocol-relative URLs allowProtocolRelative: true, // Allowed iframe hostnames allowedIframeHostnames: ["www.youtube.com", "player.vimeo.com"], // Transform tags transformTags: { a: (tagName, attribs) => { // Transform anchor tags return { tagName: "a", attribs: { ...attribs, rel: "nofollow", }, }; }, }, // Text filter textFilter: (text) => { // Filter or transform text content return text.trim(); }, }); ``` #### Default Configuration The default configuration includes: - **allowedTags**: `['h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div', 'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre']` - **allowedAttributes**: `{ a: ['href', 'name', 'target'], img: ['src'] }` - **allowedSchemes**: `['http', 'https', 'ftp', 'mailto']` - **allowProtocolRelative**: `true` - **allowedIframeHostnames**: `['www.youtube.com', 'player.vimeo.com']` ### HTML Tag Stripping The package exports `stripHtml` from `string-strip-html` for removing HTML tags and extracting plain text. #### Basic Stripping ```typescript import { stripHtml } from "@visulima/html"; // Strip HTML tags from string const result = stripHtml("Some text and text."); console.log(result.result); // 'Some text and text.' // Prevents accidental string concatenation const result2 = stripHtml("aaa
bbb
ccc"); console.log(result2.result); // 'aaa bbb ccc' // Access the stripped text const plainText = stripHtml("
Hello World
").result; // plainText: 'Hello World' ``` #### Tag Pairs with Content ```typescript import { stripHtml } from "@visulima/html"; // Strip tags together with their contents const result = stripHtml("a
void a;
b", { stripTogetherWithTheirContents: ["script", "style", "xml", "pre"], }); console.log(result.result); // 'a b' // Script and style tags are stripped by default const result2 = stripHtml("Text more text"); console.log(result2.result); // 'Text more text' // Strip style tags with their content const result3 = stripHtml("Text more text"); console.log(result3.result); // 'Text more text' ``` #### Raw Bracket Detection ```typescript import { stripHtml } from "@visulima/html"; // Detects raw, legit brackets and preserves them const result = stripHtml("a < b and c > d"); console.log(result.result); // 'a < b and c > d' // Handles comparison operators in text const result2 = stripHtml("5 < 10 and 20 > 15"); console.log(result2.result); // '5 < 10 and 20 > 15' // Handles mixed HTML tags and comparison operators const result3 = stripHtml("Value 5 < 10"); console.log(result3.result); // 'Value 5 < 10' ``` #### Advanced Options ```typescript import { stripHtml } from "@visulima/html"; // Custom tag stripping configuration const result = stripHtml(html, { // Strip these tags together with their contents stripTogetherWithTheirContents: ["script", "style", "xml", "pre", "code"], // Other options available from string-strip-html // See: https://codsen.com/os/string-strip-html/ }); // Access the stripped text const plainText = result.result; // The result object also contains other metadata // See: https://codsen.com/os/string-strip-html/ for full API ``` #### Edge Cases ```typescript import { stripHtml } from "@visulima/html"; // Handles empty strings const result1 = stripHtml(""); console.log(result1.result); // '' // Handles strings with only HTML tags const result2 = stripHtml("
"); console.log(result2.result); // '' // Handles strings with no HTML tags const result3 = stripHtml("Plain text without tags"); console.log(result3.result); // 'Plain text without tags' // Handles nested HTML tags const result4 = stripHtml("

Text bold

"); console.log(result4.result); // 'Text bold' // Handles self-closing tags const result5 = stripHtml("Line 1
Line 2
Line 3"); console.log(result5.result); // 'Line 1 Line 2 Line 3' // Handles HTML entities (they are decoded) const result6 = stripHtml("

Hello & World

"); console.log(result6.result); // 'Hello & World' ``` **When to Use Stripping vs Sanitization:** - **Use `stripHtml`** when you need plain text output and want to completely remove HTML structure - **Use `sanitizeHtml`** when you need to preserve some HTML structure while removing dangerous elements ## Related - [sanitize-html](https://github.com/apostrophecms/sanitize-html) - HTML sanitizer with a clear API - [string-strip-html](https://github.com/codsen/codsen/tree/main/packages/string-strip-html) - Strip HTML tags from strings - [html-entities](https://github.com/mdevils/html-entities) - Fast HTML entity encoding/decoding - [html-tags](https://github.com/sindresorhus/html-tags) - List of standard HTML tags - [DOMPurify](https://github.com/cure53/DOMPurify) - DOM-only, super-fast, uber-tolerant XSS sanitizer - [xss](https://github.com/leizongmin/js-xss) - XSS filter - [htmlnano](https://github.com/maltsev/htmlnano) - HTML minifier - [cssnano](https://github.com/cssnano/cssnano) - CSS minifier ## Supported Node.js Versions Libraries in this ecosystem make the best effort to track [Node.js’ release schedule](https://github.com/nodejs/release#release-schedule). Here’s [a post on why we think this is important](https://medium.com/the-node-js-collection/maintainers-should-consider-following-node-js-release-schedule-ab08ed4de71a). ## Contributing If you would like to help take a look at the [list of issues](https://github.com/visulima/visulima/issues) and check our [Contributing](.github/CONTRIBUTING.md) guidelines. > **Note:** please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. ## Credits - [Daniel Bannert](https://github.com/prisis) - [All Contributors](https://github.com/visulima/visulima/graphs/contributors) ## Made with ❤️ at Anolilab This is an open source project and will always remain free to use. If you think it's cool, please star it 🌟. [Anolilab](https://www.anolilab.com/open-source) is a Development and AI Studio. Contact us at [hello@anolilab.com](mailto:hello@anolilab.com) if you need any help with these technologies or just want to say hi! ## License The visulima html is open-sourced software licensed under the [MIT][license] [license-badge]: https://img.shields.io/npm/l/@visulima/html?style=for-the-badge [license]: https://github.com/visulima/visulima/blob/main/LICENSE [npm-downloads-badge]: https://img.shields.io/npm/dm/@visulima/html?style=for-the-badge [npm-downloads]: https://www.npmjs.com/package/@visulima/html [prs-welcome-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge [prs-welcome]: https://github.com/visulima/visulima/blob/main/.github/CONTRIBUTING.md [chat-badge]: https://img.shields.io/discord/902465130518949899.svg?style=for-the-badge [chat]: https://discord.gg/eajEZvk2PG [typescript-badge]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript [typescript-url]: https://www.typescriptlang.org/