include-fragment: a client-side includes tag for the web platform
A client-side includes tag.
At a glance
- What is it?
- The include-fragment element is a custom element that fetches an HTML fragment at runtime and swaps itself for the parsed result, with lazy loading, event hooks, nonce support and a trusted types policy hook for sanitising server responses. It suits a team that needs to include markup from another endpoint without a full page load, and the sharp edge is that the server must not respond with another include-fragment, or the element polls itself in an infinite loop.
- Who is it for?
- Adopt include-fragment if you need to pull a piece of markup from another endpoint into a page without a reload, and you control the server that answers, since the element replaces itself with whatever HTML comes back.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One element that replaces itself with fetched markup
The include-fragment element is described as a Client Side Includes tag, and the whole mechanism fits in a few sentences.
All `include-fragment` elements must have a `src` attribute from which to retrieve an HTML element fragment. The initial page load should include fallback content to be displayed if the resource could not be fetched immediately. On page load, the `include-fragment` element fetches the URL, the response is parsed into an HTML element, which replaces the `include-fragment` element entirely.
The original markup looks like this:
``` html <div class="tip"> <include-fragment src="/tips"> <p>Loading tip…</p> </include-fragment> </div>
and the result is this:
html <div class="tip"> <p>You look nice today</p> </div>
The fallback content is the part worth understanding. The element is a placeholder in the server-rendered page, and whatever is inside it is what the user sees until the fetch resolves. That is what makes this a client-side includes mechanism rather than a lazy loader, because the server sends a complete, readable document and the browser then upgrades parts of it in place.
There is a constraint on the server side that the documentation states in one sentence and that will bite you if you miss it. The server must respond with an HTML fragment to replace the `include-fragment` element, and it should not contain another `include-fragment` element or the server will be polled in an infinite loop. The reason is not hard to see: the element is replaced by the response, and if the response contains a fresh include-fragment, that one fetches again, and the cycle never terminates.
The element is a plain custom element with a `src` attribute, registered on the client side by importing the package:
js import '@github/include-fragment-element' ```
The homepage is a set of examples at github.github.io/include-fragment-element/examples, and the repository carries an `examples/` directory with an `index.html` and a `pull.html`.
Accept, loading and nonce, the three attributes that matter
The element has a small set of attributes, and each one solves a specific problem.
The `accept` attribute tells the element what to send as the `Accept` header as part of the fetch request. If omitted or set to an empty value, the default behaviour is `text/html`. The documentation explains that it is important that the server responds with HTML, but that you may wish to change the accept header to help negotiate the right content with the server. This is a content negotiation knob rather than a validation one, so a server that could return JSON for the same URL would be asked for it here.
The `loading` attribute indicates when the contents should be fetched, and it has two values. `eager` fetches and loads the content immediately, regardless of whether the element is currently within the visible viewport, and this is the default value. `lazy` defers fetching and loading the content until the element reaches a calculated distance from the viewport, with the stated intent of avoiding the network and storage bandwidth needed to handle the content until it is reasonably certain that it will be needed. The lazy mode follows the familiar pattern of image loading, and for an element that replaces itself in the DOM it means the fetched content never appears in a layout that the user never scrolled to.
The `nonce` attribute is for pages using a nonce-based Content Security Policy, and the instruction is to set the `nonce` property before the element is connected, using the fresh nonce that comes with the current response:
const loader = document.createElement('include-fragment')
loader.nonce = responseNonce
loader.src = '/tips'
document.body.append(loader)The `responseNonce` value must come from the current response, and the documentation is explicit that you should not hardcode a reusable production value. That is the correct rule and the reason is worth holding on to: a nonce is per-response by design, and a hardcoded one is a nonce that is not a nonce.
There is one more piece of behaviour worth knowing, and it is not an attribute. The request for replacement markup from the server starts when the `src` attribute becomes available on the element. Most often this happens at page load when the element is rendered, but if you omit the `src` attribute until some later time, you can defer loading the content altogether. The documentation names a real user of that technique, the details-menu element, which uses it to defer loading menu content until the menu is first opened.
Events for the request lifecycle and a cancelable replacement
Request lifecycle events are dispatched on the element, and the list is short enough to memorise.
`loadstart` fires when the server fetch has started. `load` fires when the request completed successfully. `error` fires when the request failed. `loadend` fires when the request has completed, whatever the outcome, so it is the one to use for cleanup. And then two events specific to the element.
`include-fragment-replace` is cancelable and fires when the success response has been parsed. It comes with an `event.detail.fragment` that will replace the current element. `include-fragment-replaced` fires after the element has been replaced by the fragment.
The cancelable event is the interesting one, because it is where you intercept the response before it lands in the DOM. A typical use is to add a class to the surrounding container while the request is in flight, which is what the documentation's example does:
const loader = document.querySelector('include-fragment')
const container = loader.parentElement
loader.addEventListener('loadstart', () => container.classList.add('is-loading'))
loader.addEventListener('loadend', () => container.classList.remove('is-loading'))
loader.addEventListener('load', () => container.classList.add('is-success'))
loader.addEventListener('error', () => container.classList.add('is-error'))Note the use of `loader.parentElement` as the container, which is the pattern to copy. The element is replaced when it succeeds, so any styling that depends on the element still being there has to live on its parent.
Error handling has its own small convention. If the URL fails to load, the `include-fragment` element is left in the page and tagged with an `is-error` CSS class that can be used for styling. The fallback content therefore stays visible on failure, and the class is the hook for showing a different message.
The `src` attribute is the only required one, and the options table gives it as a URL string, the required URL from which to load the replacement HTML element fragment.
Trusted types give you control over the fetched HTML
The most substantial feature is the Content Security Policy trusted types integration, and it addresses the security question that comes with injecting server responses into the page.
You can call `setCSPTrustedTypesPolicy(policy)` from JavaScript to set a CSP trusted types policy, which can perform synchronous filtering or rejection of the `fetch` response before it is inserted into the page. The documentation's example uses DOMPurify to implement it:
import IncludeFragmentElement from "include-fragment-element";
import DOMPurify from "dompurify"; // Using https://github.com/cure53/DOMPurify
// This policy removes all HTML markup except links.
const policy = trustedTypes.createPolicy("links-only", {
createHTML: (htmlText: string) => {
return DOMPurify.sanitize(htmlText, {
ALLOWED_TAGS: ["a"],
ALLOWED_ATTR: ["href"],
RETURN_TRUSTED_TYPE: true,
});
},
});
IncludeFragmentElement.setCSPTrustedTypesPolicy(policy);The policy has access to the fetch response object, but due to platform constraints only synchronous information from the response, in addition to the HTML text body, can be used in the policy. That constraint is what makes the second example possible, where the policy checks a response header before allowing the HTML through:
import IncludeFragmentElement from "include-fragment-element";
const policy = trustedTypes.createPolicy("require-server-header", {
createHTML: (htmlText: string, response: Response) => {
if (response.headers.get("X-Server-Sanitized") !== "sanitized=true") {
// Note: this will reject the contents, but the error may be caught before it shows in the JS console.
throw new Error("Rejecting HTML that was not marked by the server as sanitized.");
}
return htmlText;
},
});
IncludeFragmentElement.setCSPTrustedTypesPolicy(policy);The second example is a pattern worth copying regardless of trusted types. The server marks its own response as sanitized with a header, and the client refuses anything that lacks the mark. The comment notes that the rejection will happen but the error may be caught before it shows in the JS console, which is a good reason to also handle the `error` event.
There are three rules about the policy itself. Only a single policy can be set, shared by all IncludeFragmentElement fetches. You should call `setCSPTrustedTypesPolicy()` ahead of any other load of include-fragment-element in your code, and if your policy itself requires asynchronous work to construct you can pass a `Promise<TrustedTypePolicy>`. Passing `null` removes the policy.
Two patterns for deferring expensive markup
The Patterns section describes two ways to use the element that are worth understanding, because they are the cases where client-side includes genuinely earn their keep.
The first is a user action that begins a slow running background job on the server, like backing up files stored on the server. While the backup job is running, a progress bar is shown to the user. When it is complete, the include-fragment element is replaced with a link to the backup files.
The second is a page whose markup is expensive to generate the first time a user visits it. A loading indicator is displayed, and when the markup is finished building on the server it is stored in memcache and sent to the browser to replace the include-fragment loader. Subsequent visits to the page render the cached markup directly, without going through an include-fragment element at all.
That second pattern is the one with an interesting property, and it is worth spelling out. The include-fragment element is a cache miss marker, not a permanent part of the page. The first request pays for the generation and the fetch, and the server decides whether the second request even contains an include-fragment. The element is what makes the first request render quickly while the work happens behind it.
The alternative to this whole approach is to generate the markup before sending the page and let the user wait, which is simpler and gives the same result on a fast connection. The pattern earns its place when the generation is slow enough to matter, which is the condition the documentation is really describing.
The project itself is a good example of the ecosystem around these elements. The details-menu element is a separate custom element from the same organisation that uses this technique to defer loading menu content, and the two live side by side in the same documentation ecosystem rather than one absorbing the other.
Editorial conclusion
Adopt include-fragment if you need to pull a piece of markup from another endpoint into a page without a reload, and you control the server that answers, since the element replaces itself with whatever HTML comes back. Do not adopt it if the response can contain further include-fragment elements, because the server will be polled in an infinite loop, and check first that the endpoint returns a fragment rather than a full document, since the element parses the response as an HTML element and uses that as the replacement.
Frequently asked questions
What does the include-fragment element do?
It is a client-side includes tag. All include-fragment elements must have a src attribute from which to retrieve an HTML element fragment, and on page load the element fetches the URL, parses the response into an HTML element and replaces the include-fragment element entirely. Fallback content inside the element is shown if the resource cannot be fetched immediately.
How do I install and use include-fragment?
Run `npm install --save @github/include-fragment-element` and then import it on the client side with `import '@github/include-fragment-element'`. The element is a custom element, so it works in markup once registered, and the package also ships a define entry point for frameworks that prefer explicit registration.
What happens if the server returns another include-fragment element?
The server will be polled in an infinite loop. The server must respond with an HTML fragment and it should not contain another include-fragment element, because the replacement element would then fetch again without end.
Which events does include-fragment dispatch?
Request lifecycle events loadstart, load, error and loadend, plus include-fragment-replace, which is cancelable and carries the parsed fragment in event.detail.fragment, and include-fragment-replaced, which fires once the element has been replaced. If the URL fails to load the element stays in the page and is tagged with an is-error class.
How do I control the HTML that include-fragment inserts?
Call `setCSPTrustedTypesPolicy(policy)` with a trusted types policy that filters or rejects the fetch response before it is inserted. Only one policy can be set and it is shared by all fetches, it must be set before any other load of the element, and you can pass a Promise or null to remove it.
Is include-fragment still being maintained?
The repository is not archived and the last push was on 2026-10-20. The listed releases are v6.4.1 from 2025-09-03, v6.4.0 from 2025-05-19 and v6.3.0 from 2023-06-16, the licence is MIT, and the tests run on web-test-runner with Playwright.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/github-include-fragment-element)Was this useful?