The Growing Divide Between HTML and JavaScript
Web development has long been organized around a clean split: HTML for structure, CSS for appearance, and JavaScript for behavior. That division matches how many teams work, with designers and markup specialists on one side and JavaScript developers on the other. Recently, though, a pattern has emerged that challenges this arrangement. JavaScript frameworks such as React let developers define page structure inside JavaScript code rather than in separate HTML files. For people who work primarily with HTML and CSS, this can seem like an unwelcome violation of a sensible separation of concerns. For JavaScript developers, it solves a set of problems that become increasingly painful as applications grow.
The Traditional Model
To understand what's changing, it helps to recall how the pieces fit together in a classic web page. HTML describes what content exists and what it means. A simple shopping list, for instance, might start as markup declaring a heading, an input box, a button, and a list with two items. Open that file in a browser and the page renders. Type in the input and click the button, though, and nothing happens. The HTML cannot change itself.
CSS layers on appearance: fonts, spacing, colors. Rules written once apply consistently to any matching structure. Still, the button does nothing. That's the job of JavaScript, which handles behavior and dynamic updates. Without it, any change to a page's content requires sending data to a server and reloading the entire document—an approach that is inefficient for both browser and server on any reasonably complex page.
JavaScript enables updates without reloads. But in the traditional model, it works alongside HTML rather than inside it. The JavaScript runs after the page loads and manipulates the already-rendered structure. That arrangement functions well for simple examples. As features multiply, however, keeping the two in sync becomes a source of bugs.
Consider what happens when you add a "remove item" function to the shopping list app. The removal code needs to update both the list and the item count at the top. If the count-updating line is copy-pasted from the "add item" handler into the "remove item" handler, you now have duplicated logic. If the displayed text format changes from "(2 items)" to "Items: 2," you must find and update every copy. Miss one and the page will show inconsistent information at different moments. This is a small example; real applications multiply the problem many times over.
Two Kinds of Programming
The root of the difficulty lies in the difference between two programming styles.
Imperative programming tells the computer exactly how to do something, step by step. It is what vanilla JavaScript and libraries like jQuery use in web development. "When the user selects this element, add the .selected class to it; and when the user de-selects it, remove the .selected class from it." Every transition must be spelled out.
Declarative programming states what the result should be and leaves the how to the underlying machinery. "This element has the .selected class if the user has selected it." The tool—React, for instance—figures out the procedural steps automatically.
HTML itself is declarative. Writing a simple section with a heading and a paragraph means declaring structure; the browser's rendering engine performs the imperative work of creating elements, placing them in the document, and displaying them. JavaScript, in its natural form, is imperative. That mismatch is at the heart of the problem.
When the Pieces Get Out of Sync
Imagine a page component involving a list of checkboxes, where each row changes color when selected, summary text reports how many are checked, and buttons to select all or none enable or disable depending on the current state. With plain HTML and imperative JavaScript, this feature requires instructions for every event:
- When a checkbox is checked: mark the row's class, recount the checkboxes, update the summary text, enable or disable the "Select None" button, and disable the "Select All" button if all boxes are now checked.
- When a checkbox is unchecked: unmark the row's class, recount, update the summary, enable "Select All," and disable "Select None" if no boxes remain checked.
- When "Select All" is clicked: check all boxes, mark all rows, update text, disable "Select All," enable "Select None."
- When "Select None" is clicked: uncheck all boxes, unmark all rows, update text, enable "Select All," disable "Select None."
Each of those handlers duplicates logic shared with the others. The code has no single source of truth for the question "which checkboxes are checked?" That information lives implicitly in the checkboxes themselves, and copies of it are maintained in the row CSS classes, the summary text, and the button states. Keeping all those copies consistent is manual work, and any omission leads to a bug. This is a simple component; consider what full applications look like when every interactive part of every page needs this kind of bookkeeping.
Declaring the Result Instead
React takes the declarative approach that HTML applies to the browser and applies it to JavaScript itself. Rather than writing instructions for what to change when each event occurs, you define the page structure as a function of a single piece of state. For the checkbox example, the truth is simple: an array like checkboxValues = [false, false, true, false]. Everything else on the page is derived from that data:
- Each row element gets the
.selectedclass if its corresponding value is true. - Each checkbox is checked if its value is true.
- The summary reads "{x} of {y} selected" where x is the number of true values.
- "Select All" is enabled only when some value is false.
- "Select None" is enabled only when some value is true.
The only imperative code left in the component describes what happens on user actions: clicking a checkbox flips its value, clicking "Select All" sets everything to true, clicking "Select None" sets everything to false. Those are the only transitions you write—everything else flows from the data.
To make this work, React uses JSX, a syntax that looks like HTML but lives inside JavaScript files and can embed logic alongside the markup. When the underlying state changes, React recalculates the structure and performs the DOM updates behind the scenes. With this pattern, the summary can never contradict the checkboxes; a row cannot have the wrong class; a button cannot be enabled when it should be disabled. The entire category of bugs stemming from out-of-sync state simply disappears.
The Trade-Offs
The benefit of this approach is more than fewer bugs. Defining structure in JavaScript enables components: a block of UI can be encapsulated as reusable code rather than copied from one HTML file to another. Changes to a component update everywhere it is used, including if the component is shared across teams or applications. Components composed together form larger, still-manageable units.
That power carries costs. The most obvious is for people who previously worked only with HTML and CSS. Mixing structure into JavaScript means contributing to a page requires knowledge of the framework and the language. Files can no longer be opened and edited as plain HTML; the workflow shifts to a build toolchain.
Technical downsides exist as well. Linters and other tools that expect standard HTML can break, and third-party imperative JavaScript plugins may not integrate cleanly. JavaScript itself has well-known rough edges that developers must learn to navigate. There is also a risk that thinking in terms of abstract components leads developers to lose sight of the actual HTML being generated. Semantic tags like <section> and <aside> carry meaning that generic <div> elements lose, and that matters for accessibility—screen readers and other assistive tools depend on correct markup.
Not for Every Page
None of this means every website should be built with a JavaScript framework. A mostly static page with little interaction gains nothing from the complexity and loads slower. Even within a site that does use React, you do not need to build everything that way; one complex widget can justify the added tooling while the rest of the site stays as plain HTML. It is equally possible to overuse the pattern as to underuse it. For applications with genuine interactive complexity, though, the declarative model is a substantial improvement over hand-writing the imperative synchronization code—and that improvement is why HTML-in-JS, or something like it, is here to stay.



