{"id":15048071,"url":"https://github.com/github/jtml","last_synced_at":"2025-09-19T09:32:41.209Z","repository":{"id":37541240,"uuid":"307417139","full_name":"github/jtml","owner":"github","description":"Write HTML in JavaScript, using template-tags.","archived":true,"fork":false,"pushed_at":"2024-03-14T22:38:31.000Z","size":376,"stargazers_count":200,"open_issues_count":4,"forks_count":8,"subscribers_count":171,"default_branch":"main","last_synced_at":"2025-08-06T21:42:33.205Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/github.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-10-26T15:25:45.000Z","updated_at":"2025-01-09T19:58:44.000Z","dependencies_parsed_at":"2024-06-18T18:37:12.493Z","dependency_job_id":null,"html_url":"https://github.com/github/jtml","commit_stats":{"total_commits":65,"total_committers":6,"mean_commits":"10.833333333333334","dds":"0.23076923076923073","last_synced_commit":"a01216836e2134d7904c56c6b65e66a152df8e15"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/github/jtml","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/github%2Fjtml","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/github%2Fjtml/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/github%2Fjtml/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/github%2Fjtml/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/github","download_url":"https://codeload.github.com/github/jtml/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/github%2Fjtml/sbom","scorecard":{"id":428199,"data":{"date":"2025-08-11","repo":{"name":"github.com/github/jtml","commit":"a01216836e2134d7904c56c6b65e66a152df8e15"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.6,"checks":[{"name":"Code-Review","score":7,"reason":"Found 7/10 approved changesets -- score normalized to 7","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"project is archived","details":["Warn: Repository is archived."],"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":1,"reason":"dependency not pinned by hash detected -- score normalized to 1","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/nodejs.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/github/jtml/nodejs.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/nodejs.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/github/jtml/nodejs.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/nodejs.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/github/jtml/nodejs.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/github/jtml/publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/github/jtml/publish.yml/main?enable=pin","Warn: npmCommand not pinned by hash: .github/workflows/nodejs.yml:29","Info:   0 out of   5 GitHub-owned GitHubAction dependencies pinned","Info:   1 out of   2 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/nodejs.yml:1","Warn: no topLevel permission defined: .github/workflows/publish.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: github.com/github/.github/SECURITY.md:1","Info: Found linked content: github.com/github/.github/SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: github.com/github/.github/SECURITY.md:1","Info: Found text in security policy: github.com/github/.github/SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Branch-Protection","score":8,"reason":"branch protection is not maximal on development and all release branches","details":["Info: 'allow deletion' disabled on branch 'main'","Info: 'force pushes' disabled on branch 'main'","Info: 'branch protection settings apply to administrators' is required to merge on branch 'main'","Warn: required approving review count is 1 on branch 'main'","Info: codeowner review is required on branch 'main'","Info: status check found to merge onto on branch 'main'","Info: PRs are required in order to make changes on branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 27 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"32 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-qwcr-r2fm-qrc7","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-r7qp-cfhv-p84w","Warn: Project is vulnerable to: GHSA-q9mw-68c2-j6m5","Warn: Project is vulnerable to: GHSA-67mh-4wv8-2f99","Warn: Project is vulnerable to: GHSA-74fj-2j2h-c42q","Warn: Project is vulnerable to: GHSA-pw2r-vq6v-hr8c","Warn: Project is vulnerable to: GHSA-jchw-25xp-jwwc","Warn: Project is vulnerable to: GHSA-cxjh-pqwp-8mfp","Warn: Project is vulnerable to: GHSA-4q6p-r6v2-jvc5","Warn: Project is vulnerable to: GHSA-pfrx-2q88-qq97","Warn: Project is vulnerable to: GHSA-rc47-6667-2j5j","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-76p7-773f-r4q5","Warn: Project is vulnerable to: GHSA-25hc-qcg6-38wj","Warn: Project is vulnerable to: GHSA-qm95-pgcg-qqfq","Warn: Project is vulnerable to: GHSA-cqmj-92xf-r6r9","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-fhg7-m89q-25r3","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-19T02:43:00.835Z","repository_id":37541240,"created_at":"2025-08-19T02:43:00.835Z","updated_at":"2025-08-19T02:43:00.835Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274911578,"owners_count":25372483,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-13T02:00:10.085Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-09-24T21:07:42.964Z","updated_at":"2025-09-19T09:32:40.920Z","avatar_url":"https://github.com/github.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"### @github/jtml \n\nThis library is designed as a layer on top of [@github/template-parts](https://github.com/github/template-parts/) to provide declarative, JavaScript based HTML template tags.\n\nThis library is heavily inspired by [`lit-html`](https://github.com/Polymer/lit-html), which GitHub has used in production for a while. This was created independently from `lit-html` for the following reasons:\n\n - To re-use code we're using with [@github/template-parts](https://github.com/github/template-parts/) which is in production at GitHub.\n - To align closer to the `Template Parts` whatwg proposal. By using [@github/template-parts](https://github.com/github/template-parts/) we aim to closely align to the Template Parts proposal, hopefully one day dropping the dependency on [@github/template-parts](https://github.com/github/template-parts/).\n\n### Basic Usage\n\nThis library comes with a set of exports, the main two being `html` and `render`.\n\n`html` is a [\"tagged template\" function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). Rather than calling it, you \"tag\" a template string with `html` and it will return a `TemplateResult` which can be used to render HTML safely, on the client side.\n\n```js\nimport {html, render} from '@github/jtml'\n\nconst greeting = 'Hello'\nrender(html`\u003ch1\u003e${greeting} World\u003c/h1\u003e`, document.body)\n```\n\nThe benefit of this over, say, setting `innerHTML` is that the tagged template can be re-used efficiently, causing less mutations in the DOM:\n\n```js\nimport {html, render} from '@github/jtml'\n\nconst theTime = date =\u003e html`\u003cp\u003eThe time is ${date.toString()}\u003c/p\u003e`\nsetInterval(() =\u003e render(theTime(new Date()), document.body), 1000)\n```\n\n### Expressions\n\njtml interpolates placeholder expressions in special ways across the template. Depending on where you put a placeholder expression (the `${}` syntax is a placeholder expression) depends on what it does. _Importantly_ \"Attributes\" behave differently to \"Nodes\". Here is a comprehensive list:\n\n#### Attributes\n\nHTML Attributes can contain placeholder expressions, but these _must_ be inside the quoted part of the attribute. The name of an Attribute cannot use placeholder expressions, only the value.\n\n```js\nimport {html, render} from '@github/jtml'\n\nconst className = `red-box`\n\nhtml`\u003cp class=\"${className}\"\u003e\u003c/p\u003e` // This is valid\n\nhtml`\u003cp class=${className}\u003e\u003c/p\u003e` // !! This is INVALID!\nhtml`\u003cp ${attr}=\"test\"\u003e\u003c/p\u003e` // !! This is INVALID!\n```\n\n##### Boolean Values\n\nIf an attribute maps to a [\"boolean attribute\"](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes#boolean_attributes), and the attribute value consists _solely_ of a placeholder expression which evaluates to a boolean, then this can be used to toggle the attribute on or off. For example:\n\n```js\nimport {html, render} from '@github/jtml'\n\nconst input = (required = false) =\u003e html`\u003cinput required=\"${required}\" /\u003e`\nconst div = (hidden = false) =\u003e html`\u003cdiv hidden=\"${hidden}\"\u003e\u003c/div\u003e`\n\nrender(input(false), document.body) // Will render `\u003cinput /\u003e`\nrender(input(true), document.body) // Will render `\u003cinput required /\u003e`\n\nrender(div(true), document.body) // Will render `\u003cdiv\u003e\u003c/div\u003e`\nrender(div(false), document.body) // Will render `\u003cdiv\u003e\u003c/div\u003e`\n```\n\n##### Multiple values, whitespace\n\nIf an attribute consists of multiple placeholder expressions, these will all be mapped to strings. Any included whitespace is also rendered as you might expect. Here's an example:\n\n```js\nimport {html, render} from '@github/jtml'\n\nconst p = ({classOne, classTwo, classThree}) =\u003e html`\u003cp class=\"${classOne} ${classTwo} ${classThree}\"\u003e\u003c/p\u003e`\n\nrender(p({classOne: 'red', classTwo: 'box', classThree: ''}), document.body)\n// ^ Renders `\u003cp class=\"red box  \"\u003e\u003c/p\u003e`\n\nconst i = ({classOne, classTwo}) =\u003e html`\u003ci class=\"${classOne}-${classTwo}\"\u003e\u003c/i\u003e`\n\nrender(i({classOne: 'red', classTwo: 'box'}), document.body)\n// ^ Renders `\u003ci class=\"red-box\"\u003e\u003c/i\u003e`\n```\n\n##### Iterables (like Arrays)\n\nAny placeholder expression which evaluates to an Array/Iterable is joined with spaces (`Array.from(value).join(' ')`). This means you can pass in an Array of strings and it'll be rendered as a space separated list. These can still be mixed with other placeholder expressions or static values. An example:\n\n```js\nimport {html, render} from '@github/jtml'\n\nconst p = ({classes, hidden = false}) =\u003e html`\u003cp class=\"bold ${classes} ${hidden ? 'd-none' : ''}\"\u003e\u003c/p\u003e\n\nrender(p({classes: ['red', 'box'], hidden: true}), document.body)\n// ^ Renders `\u003cp class=\"bold red box d-none\"\u003e\u003c/p\u003e`\n\nrender(p({classes: ['red', 'box'], hidden: false}), document.body)\n// ^ Renders `\u003cp class=\"bold red box \"\u003e\u003c/p\u003e`\n```\n\n##### Events\n\nIf an attributes name begins with `on`, and the value consists of a single placeholder expression that evaluates to a function, then this will become an Event Listener, where the event name is the attribute name without the `on`, so for example:\n\n```js\nimport {html, render} from '@github/jtml'\n\nconst handleClick = e =\u003e console.log('User clicked!')\n\nrender(html`\u003cbutton onclick=\"${handleClick}\"\u003e\u003c/button\u003e`, document.body)\n// ^ Renders `\u003cbutton\u003e\u003c/button\u003e`\n// Effectively calls `button.addEventListener('click', handleClick)`\n```\n\nThe event name can be any event name that is also possible as an attribute, for example `onloaded` will listen for the `loaded` event, `onwill-load` will bind to the `will-load` event. Special characters such as `:`s are not allowed as attribute names, and as such you cannot bind to an event name with these special characters using this pattern.\n\n#### Nodes\n\nPlaceholder expressions can also be put where an HTML node might be - in other words inside a tag, rather than inside an attribute. These behave differently to placeholder expressions inside attribute values:\n\n##### HTML Escaping\n\nAny HTML inside a string is automatically escaped. Values get added as `Text` nodes, meaning it is impossible to inject HTML unless you explicitly want to, making them safe for XSS. This is not manually handled by the library, but is core to the design - meaning the browser handles this escaping! An example:\n\n```js\nimport {html, render} from '@github/jtml'\n\nconst unsafe = '\u003cscript\u003ealert(1)\u003c/script\u003e'\n\nrender(html`\u003cdiv\u003e${unsafe}\u003c/div\u003e`, document.body)\n// ^ Renders `\u003cdiv\u003e\u0026lt;script\u0026gt;alert(1)\u0026lt;/script\u0026gt;\u003c/div\u003e`\n```\n\n##### Sub Templates\n\nIf a placeholder expression evaluates to a sub template, then that sub template will be rendered and added to as a child to the node, in the position you'd expect:\n\n```js\nimport {html, render} from '@github/jtml'\n\nconst embolden = word =\u003e html`\u003cstrong\u003e${word}\u003c/strong\u003e`\n\nrender(html`\u003cdiv\u003eHello ${embolden('world')}!\u003c/div\u003e`, document.body)\n// ^ Renders `\u003cdiv\u003eHello \u003cstrong\u003eworld\u003c/strong\u003e!\u003c/div\u003e`\n```\n\n#### Document Fragments\n\nYou can also pass document fragments in, and they will be rendered as you might expect. This is useful for mixing-and-matching template libraries:\n\n```js\nimport {html, render} from '@github/jtml'\n\nconst vanillaEmbolden = word =\u003e {\n  const frag = document.createDocumentFragment()\n  const strong = document.createElement('strong')\n  strong.append(String(word))\n  frag.append(strong)\n  return frag\n}\n\nrender(html`\u003cdiv\u003eHello ${vanillaEmbolden('world')}!\u003c/div\u003e`, document.body)\n// ^ Renders `\u003cdiv\u003eHello \u003cstrong\u003eworld\u003c/strong\u003e!\u003c/div\u003e`\n```\n\n##### Iterables (like Arrays)\n\nAny placeholder expression which evaluates to an Array/Iterable is evaluated per-item. If a single item is a Document Fragment or Sub Template then it will be rendered as you might expect, otherwise it is treated as a String and gets added as a `Text` node. All of the contents of the Array will be rendered as one. Some examples:\n\n```js\nimport {html, render} from '@github/jtml'\n\nconst data = [ { name: 'Spanner', value: 5 }, { name: 'Wrench', value: 5 } ]\n\nconst row = ({name, value}) =\u003e html`\u003ctr\u003e\u003ctd\u003e${name}\u003c/td\u003e\u003ctd\u003e${value}\u003c/td\u003e\u003c/td\u003e`\nconst table = rows =\u003e html`\u003ctable\u003e${rows.map(row)}\u003c/table\u003e`\n\nrender(table(data), document.body)\n// ^ Renders \n// \u003ctable\u003e\n//   \u003ctr\u003e\u003ctd\u003eSpanner\u003c/td\u003e\u003ctd\u003e5\u003c/td\u003e\u003c/tr\u003e\n//   \u003ctr\u003e\u003ctd\u003eWrench\u003c/td\u003e\u003ctd\u003e5\u003c/td\u003e\u003c/tr\u003e\n// \u003c/table\u003e\n```\n\n### Directives\n\nFor more advanced behaviours, a function can be wrapped with the `directive` function to create a `Directive` which gets to customize the rendering flow. jtml also includes some built in directives (see below). \n\nA directive must follow the following signature. It can take any number of arguments (which are ignored) and must return a function which receives the TemplatePart:\n\n```typescript\ntype Directive = (...values: unknown[]) =\u003e (part: TemplatePart) =\u003e void\n```\n\nHere's an example of how a directive might work:\n\n```js\nimport {html, render, directive} from '@github/jtml'\n\n// A directive can take any number of arguments, and must return a function that takes a `TemplatePart`.\nconst renderLater = directive((text, ms) =\u003e part =\u003e {\n  // A parts value can be set using `.value`\n  part.value = 'Loading...'\n  setTimeout(() =\u003e part.value = text, ms)\n})\n\nrender(html`\u003cdiv\u003e${renderLater('Hello world', 1000)}`, document.body)\n// ^ Renders \u003cdiv\u003eLoading...\u003c/div\u003e\n// After 1000ms, changes to `\u003cdiv\u003eHello world\u003c/div\u003e`\n```\n\n### Built in Directives\n\n### until\n\njtml ships with a built-in directive for handling Promise values, called `until`. `until` takes any number of Promises, and will render them, right to left, as they resolve. This is useful for passing in asynchronous values as the first arguments, timeout messages as the middle value, and synchronous values for the placeholder values, like so:\n\n\n```js\nimport {html, render, until} from '@github/jtml'\n\nconst delay = (ms, value) =\u003e new Promise(resolve =\u003e setTimeout(resolve, ms, value))\n\nconst request = delay(1000, 'Hello World')\nconst loading = 'Loading...'\nconst timeout = delay(2000, 'Failed to load')\n\nrender(html`\u003cdiv\u003e${until(request, timeout, loading)}\u003c/div\u003e`)\n// ^ renders \u003cdiv\u003eLoading...\u003c/div\u003e\n// After 1000ms will render \u003cdiv\u003eHello World\u003c/div\u003e\n```\n\n```js\nimport {html, render, until} from '@github/jtml'\n\nconst delay = (ms, value) =\u003e new Promise(resolve =\u003e setTimeout(resolve, ms, value))\n\nconst request = delay(3000, 'Hello World') // Request takes longer than the timeout\nconst loading = 'Loading...'\nconst timeout = delay(2000, 'Failed to load')\n\nrender(html`\u003cdiv\u003e${until(request, timeout, loading)}\u003c/div\u003e`)\n// ^ renders \u003cdiv\u003eLoading...\u003c/div\u003e\n// After 2000ms will render \u003cdiv\u003eFailed to load\u003c/div\u003e\n```\n\n### CSP Trusted Types\n\nYou can call `TemplateResult.setCSPTrustedTypesPolicy(policy: TrustedTypePolicy | Promise\u003cTrustedTypePolicy\u003e | null)` from JavaScript to set a [CSP trusted types policy](https://web.dev/trusted-types/), which can perform (synchronous) filtering or rejection of the rendered template:\n\n```ts\nimport {TemplateResult} from \"@github/jtml\";\nimport DOMPurify from \"dompurify\"; // Using https://github.com/cure53/DOMPurify\n\n// This policy removes all HTML markup except links.\nconst policy = trustedTypes.createPolicy(\"links-only\", {\n  createHTML: (htmlText: string) =\u003e {\n    return DOMPurify.sanitize(htmlText, {\n      ALLOWED_TAGS: [\"a\"],\n      ALLOWED_ATTR: [\"href\"],\n      RETURN_TRUSTED_TYPE: true,\n    });\n  },\n});\nTemplateResult.setCSPTrustedTypesPolicy(policy);\n```\n\nNote that:\n\n- Only a single policy can be set, shared by all `render` and `unsafeHTML` calls.\n- You should call `TemplateResult.setCSPTrustedTypesPolicy()` ahead of any other call of `@github/jtml` in your code.\n- Not all browsers [support the trusted types API in JavaScript](https://caniuse.com/mdn-api_trustedtypes). You may want to use the [recommended tinyfill](https://github.com/w3c/trusted-types#tinyfill) to construct a policy without causing issues in other browsers.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgithub%2Fjtml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgithub%2Fjtml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgithub%2Fjtml/lists"}