Seeking an Answer: Why can’t HTML alone do includes?

Chris Coyier Chris Coyier on

I’m obsessed with this basic web need.

You’ve got three pages:

  • index.html
  • about.html
  • contact.html

You need to put the same header on all three pages.

Our developer brains scream at us to ensure that we’re not copying the exact code three times, we’re creating the header once then “including” it on the three (or a thousand) other pages.

There are so many solutions to this problem

We don’t need to list them all here. I documented some of them one time, but there are many more.

We’ve got JavaScript to go fetch the HTML and insert it. We’ve got old school web server directives. Any static site generator can do it. Task runners can do it. Templating languages tend to have include functionality. Any backend language can generate HTML on the fly. I’ve seen several Web Components purpose-built for this. We’ve got <iframe>, which technically is a pure HTML solution, but they are bad for overall performance, accessibility, and generally extremely awkward here, but we can extract them. We can just not worry about includes at all and trust our code editors powerful find and replace tools.

But none of the solutions is HTML

None of these are a straightforward HTML tag. None of these are just some HTML way of saying “go get this HTML and put it here”, like <img> is for “go get this image and put it here.

Why?

No, really, why?

CSS can import CSS. JavaScript can import JavaScript. HTML cannot import HTML.

I’m sure there are reasons. Normally, web standards and browsers are all about “paving the cowpaths”, that is, providing solutions around what developers are already doing. We were reaching for third-party JavaScript to help with dates, so the web platform stepped in. We were using frameworks for page transitions, so the web platform stepped in. We used libraries for positioning elements next to each other safely, so the web platform stepped in. There are countless examples like this.

I’d wager (counts fingers) pretty much all websites need this ability, and all of them reach for different additional non-web-standard tooling to get it done. That’s weird.

So what are those reasons?

  • Does it wreck the preload scanner? As in, it could have very bad web performance issues?
  • Would it have to be async and thus have issues with jerky/shifty loading experiences?
  • Does it introduce some kind of complexity that harms the purity of HTML or something?
  • Is it the difficulty of handling nested includes? Circular includes?
  • Is it pushback from web hosting that a feature like this would increase web requests dramatically?
  • Would the restrictions be too strict to make it useful? Like for whatever reason it’s cool to load images and CSS and JS from other domains, but HTML would likely be restricted.
  • Is there something problematic about the idea that I haven’t listed here?
  • Is there actually no real desire for this and I’m just a weirdo thinking there is?

If anyone has been a part of these discussions or knows exactly why this is, or you just have general thoughts, leave a comment!

Looking for a complete course on getting into web development?

Frontend Masters logo

We have a complete intro course to web development by renowned developer Brian Holt from Microsoft. You'll learn how to be a successful coder knowing everything from practical HTML and CSS to modern JavaScript to Git and basic back-end development.

7-Day Free Trial

49 responses to “Seeking an Answer: Why can’t HTML alone do includes?”

  1. pd says:

    Broadly speaking, there’s one simple reason:

    HTML became rhe deathly standard, not a “living standard”.

    Look at how many tags have been born since HTML5 became the final version of HTML.

    Then compare that to how many tags we got before HTML5.

    Rather than being some endless source of ongoing innovation that perhaps suited browser developer desires to release support for one tag this month, two tags another month … ‘rolling release’ fashion … without targeting a HTML5, we’ve ended up with a pathetic trickle of very few new tags and then the “well, do it yourself with ‘web components'”.

    So now there’s all this confusion between framework components and all the guff around ‘web components’ like shadow bloody DOMs and so forth.

    Meanwhile everyone’s hyper over-engineered their publishing to the point of requiring compilation. What, did we all want to feel like legitimate desktop developers?

    Then everyone’s getting rather excited about an elementary ‘no spoilers’ tag combo that is details and summary.

    Basically what I’m trying to say is actual HTML evolution died when theyv killed major versions and pretended to leave a ‘living standard’ in it’s place. Ignoring all the needs developers have had to implement by other means. For example, the topic of this article was addressed by jQuery about 15 years ago.

  2. I just use hxincl, part of the W3C’s HTML-XML-tools suite, to handle includes in my makefile. If I need something with variables, I can use m4 macros. It’s still not HTML, but it works well enough for my purposes.

  3. Chris Smith says:

    You’re not alone, Chris. This has been bugging me for over 20 years. I don’t have any answers.

  4. Hey Chris, have you been following the associated and heavily-discussed GitHub Issue? https://github.com/whatwg/html/issues/2791

    I assume the answer lies in a number of factors:

    • low priority
    • poor demand
    • existing implementations (albeit not pure HTML)
    • apathy
    • no clear spec proposal
    • bureaucracy

    I, like you, would love a declarative Include, performance pains or not. Cached includes could be amazing!

  5. Ok a few thoughts. Some of which are cross-domain issues.

    the big one is that a block of html isn’t just html. if it was, hey, great, no problem. render it and move on.

    but a block of html (like in a web-component or a react component) can contain css and scripts and meta tags and the like. each of these needs rules for when it runs and applies, and as such each also needs to be handled by CSP CORS rules in some manner, even if on the same domain.

    Granted, none of these rules existed X years ago when this could have been implemented where security concerns would have just addressed the fact that it would have been there.

    But now the security concerns are SO large that it is just unfeasible to address them all.

    that’s one thing the JS solutions all are kind of stuck, trying to handle that security concern as best they can give or take not knowing when the browser is going to reject something.

    Second is yes this affects rendering, particularly if the included html has a meta tag that changes the device-width. Synchronous processing would work albeit with a risk of slowdown much like how a script tag without defer freezes the render while waiting for the load. but if async, do we have a ‘loading’ tag to show what it had before it is loaded?

    now in all of that, when does “ready” fire? when does “on document load” fire? do we need a new event for “document ready AND all the other crap is loaded, too”?

    Now throw in the next step: what happens in when the html included by the “” tag…has tags? they have to be loaded, too. so how does the above document ready thing know when they’re all done, too?

    And finally the curse: recursion. X includes Y includes X and the loop is infinite. yeah code could be done to test for X…but if Y doesn’t include X but rather, through a script, includes X?date=new Date().milliseconds – so there’s no way for the browser to cache it?

    yeah, thoughts. None of them positive, really, and all leading to the idea that the server should just handle this, or web-components that are clearly restricted in what they can include.

    • Stevo says:

      Yes you are correct. The world is based on rules (in some parts anyway….), so rule being a return response relating to a script will be rejected. No dynamic loading of scripts. Remove iframes from the standard. Or at least remove the event listeners that allow communication between frame and parent dom. Then there may be a chance??

    • flinindorf says:

      How is that any different from Javascript? Javascript can have html, css, and other Javascript, make network requests etc.

      And you could get most of the benefit without most of the security risks if html include was only allowed from the same origin.

  6. jafar says:

    My 2 cents. It’s the HTML protocol itself and document format. If you have includes something has to transpile that because it’s now out of spec from being transferred. So you need to update the protocol for html.

    If you put something that doesnt have a directive, and one might argue that IFrame is that directive, that the HTML wont know what to do with it or make its transfer over the network.

    • Chris Coyier says:

      Those were all about instantiating web components I’m pretty sure, so perhaps a similar problem set but not the same as “go get this HTML and inject it here”.

  7. Although it is a small JavaScript library, HTMX is really close to a pure HTML solution. You only need to add HTMX to the head, and the rest is done in HTML.
    A pure HTML solution was Carson Daily’s goal when developing HTMX.

  8. DeLorean Time Machine says:

    Hmm, trying again:

    <object data="footer.html">Error text </object>
    
    • Chris Coyier says:

      That’s pretty similar to an iframe right? Absolutely no styles in? The contents inside have no effect on the layout of the content outside, etc?

      • DeLorean Time Machine says:

        I think so. Believe it has a bit less separation, better defaults than an iframe. Can have its own local styles, scripts. I’d share an external stylesheet to share styles.

        Can reach parent document with window.parent and anchor target=top.

      • It doesn’t cascade but you can have a separate style sheet linked inside the embedded document or link to the same style sheet as the containing document.

    • This is the correct answer.

  9. Luis says:

    I think one of the core ideas behind how the web platform works is that HTML is supposed to be the result of logic, not the place where the logic actually runs. But as developers, we keep trying to make HTML ‘do logic’ anyway. We want it to handle includes, loops, conditionals, or dynamic content. That mismatch between what HTML was built for and what we expect from it is exactly why native includes seem like they should exist. But they don’t, because adding them would blur the line between markup and logic, and that’s something the platform has always been careful to keep separate.

  10. Ernest says:

    I’m pulling this entirely out of my ass, but I think it’s likely that the people who would benefit the most from this (i.e. those that aren’t already using one of the hack-ish alternatives)–are the people who don’t have a voice in these discussions. Small devs making a personal site, with minimal JS, probably without a framework. “Indie web”-type stuff.

    From what I can tell hanging out in the WICG web components discord, the people pushing for web components tend to be the kind of people who write frameworks. They just don’t care that much about “pure HTML” because what they have is “good enough” already. There have been countless github issues requesting this, and a lot of the time, they’re shot down with “javascript modules work well enough, why do we need this”.

    Anecdotally–I’m just a guy. I listen to Shop Talk once a week. I like to make small, static sites for projects. If “HTML modules” isn’t a thing, I’m not going to go to the WICG and write a proposal. I’m terrified of talking to these people, much smarter than me, who spend all day talking about the esoterics of the shadow DOM. There’s no world in which I could push these proposals forward by “declaring an intent to prototype”.

    I just keep checking these discussions, hoping that something will magically change >.>

  11. Yeah, it’s wild that HTML still doesn’t have a native way to include fragments. I think the main blockers are performance (preload scanner), layout shift issues, security concerns (scripts, DOM collisions), and complexity around nested or circular includes. Everyone solved it with JS or build tools, so the pressure to standardize kind of fizzled.

  12. Phil says:

    Semantic landmarks are literally designed to be included as individual blocks, ironically had they introduced those with includes functionality originally we’d be looking at a whole different internet today.

  13. Jer Lloyd says:

    Well, I think the “reason” for this, is the design principle that html is stateless. Now, I can think of like, several examples where that has been contradicted. “Details/Summary”, the element, I’m sure there are loads more, I haven’t been professionally coding very long at all, really, but I think that design philosophy is pretty protected and enforced by whoever protects and enforces the rules of html, and that the crucial fundamental nature of that functionality, combined with the context of the ubiquity of solutions for that, and especially, taking into consideration that the push towards html hasn’t been very circular at all until recently, as framework bloat and complexity creep has continued to mature, that yeah, it’s too big of a change, it has solutions and it’s contrary to the hypertext core philosophy.

    Now, SHOULD html be able to do that? Hell yeah. Being stateless sucks lol. There are super powerful benefits, cost evasions, and a digital data nimbleness that comes with it though. Flat, static, unmodified or obstructed html loads fast as hell. That’s powerful and valuable. So maybe that loss is worth it. (But I say no way. Patch that in there. Let me run vanilla html and css and only bring in js if I need logic. Let’s build for 2025 and beyond, not 1993. Just saying.)

    I really enjoyed this article a lot by the way. Super cool. Insightful. Stimulates useful, educational, valuable discussions. Excellent choice. 👍

  14. Stevo says:

    As Daniel T Sasser said htmx is the closest to this.
    From hypermedia.systems:
    You may be more familiar with the term hypertext, from whose Wikipedia page the above quote is taken. Hypertext is a sub-category of hypermedia and much of this book is going to discuss how to build modern applications using hypertexts such as HTML, the Hypertext Markup Language, or HXML, a hypertext used by the Hyperview mobile hypermedia system.
    Having thought about it for a hot minute, I think this would require a front and back end solution, but not impossible. Think of an element with a hidden attribute. The front end (browser) knows what to do when it encounters this. Back end doesn’t know anything about it, it just serves it up. So let’s say there’s a standard implementation of all html that enforces a body->header->content->footer structure. And for each of these major elements there was an attribute like persist. When a http request is issued, headers indicate whether there is a persist attribute for each of these major elements. ie: html-persist: header. Server then parses the file and omits the header potion of the html. Returns all other elements except the persist attributed elements. Browser renders the returned elements.
    What is the cost? Some server side filtering?
    Could be done and would likely speed up fetches when extended to head elements.

  15. Stevo says:

    Oh and good question too. It has totally baked my noodle over the years (until I discovered templates in go just recently) why this isn’t a standard built into html. Years ago with php did a minimalistic framework like this but it wasn’t fun (don’t like php….)

  16. Jeremy says:

    It’s not exactly an include but, there is the hated IFRAME tag. You can use it to include a header with a common menu and head image and footer…( not HEAD tag with common JS and CSS which is really why you might want to do this ), but it can provide some basic commonality across pages. I think once upon a time you could have gotten away with a common js file targetting the parent but now there are too many restrictions. And rightly so.

  17. Adam says:

    100% with you. Some of the HTML/JS/CSS missing features are stunning. Especially when you think after 30 years each page loaded up is a little unicorn that’s hand made on each load. It’s crazy how so little has been made, I’d list out wants but with AI coming I’m sure it’ll do better.

  18. Zacky Ma says:

    I think it’d be useful to differentiating HTML code/template from HTML files/documents.

    In most cases, what people want to include or import is HTML code, like a header, a footer, or some HTML code/template to create the internal structure of a custom element.

    But an HTML file/document (foo.html) would require a valid structure from doctype to charset to <title>, and so on.

    I’d love to have a client-side declarative way to include HTML templates from other files, but I’d like to have a different file extension than .html, maybe .tpl or .part.

  19. Luke Dary says:

    Reposting from https://w3c.social/@lukedary/114433460736579129

    I hesitate to bring this alternative up, but XSLT does provide this mechanism, but would mean you’re serving XML files instead of HTML. I frequently end up asking myself “Why don’t we use XML for more things?” and haven’t ever come up with a good answer.

    • Eion says:

      I’ve done this for a project before. The xml doc was just xhtml of the content then xslt to client-side render the outside html tags and css and header and footer.

      Worked really well, just a pain in the butt making sure everything was valid strict xml with no wriggle room of the looser xhtml spec

  20. ChatGPT gives a great answer WHY HTML Import got cancelled.

    Most Web Component builders add way too many bells and whistles.

    This 7 line Web Component code does the basics: import content. And added functionality to move styles to shadowDOM, or just replace the whole Component with the loaded content. (delete 2 codelines if that’s too fancy)

    customElements.define(“load-file”, class extends HTMLElement {
    async connectedCallback(
    src = this.getAttribute(“src”),
    shadowRoot = this.shadowRoot || this.attachShadow({ mode: “open” })
    ) {
    shadowRoot.innerHTML = await (await fetch(src)).text()
    shadowRoot.append(…this.querySelectorAll(“[shadowRoot]”))
    this.hasAttribute(“replaceWith”) && this.replaceWith(…shadowRoot.childNodes)
    }
    })

  21. Gabriele says:

    It used to, to a degree, with SGML entities.

    XML could with external entities, but much better with Xlnclude.

    Of course someone decided to throw everything XML away, and here we are.

  22. Do the supposed problems with HTML imports not apply to server-side includes? I am inclined to think that the real problem with HTML imports is that they compete with the other soulutions mentioned and advoctes of those solutions think they are doing you a favor by forcing you to learn their favorite.

    • Justin Cummings says:

      I kinda feel this applies. If we had native directives that allow pure html imports or composability and translation of html events to native handlers, and perhaps other native directives that reduced the need for dynamic solutions, less people would require JavaScript. JavaScript enables many good things but also many not so good things.. outright bad things and some less insidious, like ads and trackers. The less insidious things is what pays for the web for now, so I feel that is a factor at some level.

      FWIW, there is another way of loading external resources and deferring renders, adjusting layout to the eventual render result: img.

  23. Love the idea, but not sure the best way it should bet implemented.

    Would the included file be parsed separately and the html nodes be added into the tree to replace the “include” tag, or should the text of the included file be inserted into the original document?

    Could you, for example, have an opening tag in the “header” and it’s closing tag in the “footer”? What would happen if there was an error including the header or the footer or both? How should the browser behave?

    • Justin Cummings says:

      I think this is also a factor. Time traveling back to HTML’s ancestor, Sgml, they had an elaborate system of doctype declarations that relied on several aspects to make native composition happen: dtd complexity and failure handling inconsistency between renderers look like they contributed most of the pain.

  24. Pouya Kary says:

    You’re damn right. It was designed to be human author-able and now you literally can’t have a functioning website without a generator or a server in the middle.

    • Chris Coyier says:

      I guess things aren’t any better or worse than they started in that regard, but that is kinda what I’m getting at. Wouldn’t it be nice to be able to get a little further down the road without having to introduce tooling?

  25. That is the reason that forces me to use a static website generator (mainly eleventy). You cant develop a simple site using only HTML without repeating all your code.

  26. Sree Kotay says:

    Here is an example – I tried pasting here but all the HTML formatting got stripped (which makes sense lol)

    so in gist form:
    https://gist.github.com/sreekotay/08f9dfcd7553abb8f1bb17375d601633

  27. Evert says:

    Like others said before, I’ve definitely found an answer in HTMX.

  28. Karen ann says:

    Isn’t php the simplest tool here?

  29. Stoyan says:

    Add my joke of a framework Lizzy.js to the list of tools 😉

  30. Martin says:

    I feel this is a case of “Read the Fine Standard” – too many call themselves ‘web developers’ and never have … and never understood that does exactly that. In fact, it has since HTML 4.0 in 1997 (implementation by browsers may have varied).

    So … please, please, instead of chasing rabbits, do read the standard. It enlightens.

  31. Andy Davies says:

    Imagine a world in which HTNL imports existed… what choice should the browser make when it encounters an include for say header.html in index.html

    Should the browser wait for header.html to be fetched before rendering the rest of the page below it?

    Or should the browser render the rest of the page while header.html it being fetch and then potentially shift the content around when the import renders?

    Of course the layout shift can be reduced by reserving space but then the parent needs to know about the child

    IMV Server-Side and Edge Side includes are a better way of addressing the issue – assemble that page from it’s components and cache the result before sending it to the client

    Ikea do this https://medium.com/flat-pack-tech/server-side-rendering-ikea-com-with-edge-computing-fcce97f6314f

  32. nobody says:

    can do this. html5 supports svg.

  33. Vlad says:

    Because HTML was never built for logic-based content reuse, and implementing it would require solving deep problems in performance, security, complexity, and browser architecture—problems that are already better handled by existing tools like static site generators and frameworks.

Leave a Reply to Deividas Strole Cancel reply

Your email address will not be published. Required fields are marked *

Did you know?

Frontend Masters Donates to open source projects. $363,806 contributed to date.