Existing JavaScript applications can adopt htmx before changing their templates
An existing JavaScript application can introduce htmx while keeping its current HTML templates. Developers can connect htmx to their JavaScript first, then decide which behaviors to express in HTML later. This separates initial integration from longer-term design and lets existing JavaScript stay in place during adoption.
htmx centers page interaction on HTML attributes: developers declare what an element should do, and htmx runs the JavaScript that carries out that behavior. Its JavaScript interfaces also support selective adoption or complete migration, so teams can add htmx to existing pages, use it in new application sections, and replace older behavior over time. Starting with attributes is straightforward on a new page, but an existing application may need changes to rendering code and server templates first.
Declarative HTML remains the preferred model
Those template changes enable htmx's main benefit: developers can describe extensive page interaction without writing JavaScript for every behavior. This is a declarative model, meaning the HTML states the required behavior through the hx- family of attributes while htmx handles execution. A new application fits this model well because developers can start with a non-interactive page and add declarations as they build.
Custom behavior can follow the same model through hx-on, which connects an event to an inline code block or an existing function. The attribute names the event, and its value supplies the JavaScript to execute. For example, this button runs JavaScript when clicked:
Here, hx-on:click connects the button's click event to console.log(). The template string inserts the value of this into `$ clicked`, so the console message includes the element handling the event. Keeping the code beside the button makes its behavior visible in the markup, consistent with surrounding interactions expressed through hx- attributes.
Keeping behavior in the markup also determines where developers must edit it. Adding an inline event attribute usually requires changing the code that renders the element, which may include a server-side template. Developers who want custom behavior outside those attributes can register it through JavaScript, accepting more setup work in exchange for a different integration point.
A project in mind?
Schedule a 30-minute meeting with us.
Senior experts helping you move faster across product, engineering, cloud & AI.
The htmx API provides a template-preserving entry path
JavaScript integration starts with the htmx object and its application programming interface, or API: the methods code calls to attach or trigger behavior. Every behavior available through htmx attributes can also be invoked through this API. So existing code can configure htmx before developers add attributes to the affected elements.
Event handling offers a direct example through htmx.on(), which registers an event listener, a function that runs when an event occurs. With hx-on, the HTML attribute connects the event to its behavior; with htmx.on(), JavaScript defines that connection. Developers can change the registration independently of a server-side template, but their code must attach the listener to the relevant elements in the DOM, the browser's representation of the page.
Once an event needs to fetch content, htmx.ajax() provides a programmatic request interface. Existing JavaScript starts the request, then htmx processes the response and inserts the result into the DOM. This lets a team introduce htmx's response handling while keeping the code that starts the interaction in JavaScript.
Inserted content needs setup too. htmx.onLoad() handles this by registering a callback, a function invoked at a particular point in processing. When htmx fetches content and adds it to the DOM, it processes that content before running these callbacks, so the callback provides a place to initialize new content after htmx has finished its work.
The content's attributes determine how much initialization htmx performs itself. When inserted markup contains hx- attributes, htmx processes them and establishes the declared behaviors; additional custom behavior needs its own setup. Developers can use the callback to identify the inserted content, account for what htmx has already set up, and attach the remaining JavaScript, including when the content has no hx- attributes.
These interfaces cover three connected tasks: registering an event, requesting and inserting content, and initializing that content after processing. A team can implement that sequence while preserving its current template approach, but it still has to coordinate event handlers, requests, and DOM changes.
This coordination work also explains the architectural trade-off. Using the API throughout an application keeps more behavior in imperative JavaScript, where developers explicitly arrange what happens and when, and gives up some of htmx's declarative advantage. It can be a useful transition when immediate template changes are undesirable, while leaving teams free to move those connections into HTML later or retain selected custom JavaScript.
q() brings htmx-style operations into existing JavaScript
Alongside the API methods, htmx 4 introduces hx-live, which uses the q() function to select elements and perform operations with syntax resembling htmx attributes. Selection and subsequent operations become more concise than equivalent code built around document.querySelector().
The selection model helps when an interaction on one element affects several others. A button can select a group and apply an operation to every matching element. This example places the call inside an inline event attribute:
When the button is clicked, q('.item') selects every element with the class item, and .toggle('.selected') toggles the selected class on each one. The button's "Select all" label therefore needs care: selected elements lose the class, and unselected elements gain it. The operation expresses a group-wide toggle.
Other operations define different relationships within a group. With take(), a class or attribute is removed from the selected elements and added to the element making the call. A button can, for example, take a state from its peer buttons inside a div, expressing both the group's change and the calling element's change in one operation.
The same query-based approach applies to events and markup. trigger() dispatches an event to every matching element and offers an option to prevent bubbling, the movement of an event through an element's ancestors. insert() adds HTML inside or around the selected targets, so both operations use the query to determine which parts of the DOM receive the change.
Although the button example places q() in an HTML attribute, ordinary JavaScript functions and callbacks can call it too. An existing application can add these operations alongside its current JavaScript, extend its htmx use, and replace legacy functionality over time. Teams can choose the stages that fit the application, including selective adoption that leaves some existing behavior in place.
Custom JavaScript and reactive frameworks require different decisions
Keeping selected JavaScript is one integration decision; combining systems that independently react to events and update the page is another. These reactive systems can interfere with one another, causing side effects, conflicts, and unpredictable behavior. The recommended arrangement is for htmx and JavaScript deliberately integrated with it to manage the page's reactive behavior, giving custom code a defined place in htmx's processing and DOM changes.
Alpine.js is an expressly supported companion with a similar attribute-oriented approach. It adds behavior through attributes beginning with x-, including x-on:click for click handling, which fits an HTML-oriented implementation. Yet sharing that style also means Alpine and htmx may operate on the same component, so developers must coordinate their effects.
The hx-alpine-compat extension helps by keeping Alpine behavior enabled and active in components handled by htmx. Alpine's behavior can therefore continue as htmx works on those components. The extension handles this part of coexistence, while developers remain responsible for checking how the two systems' behaviors interact.
InfoWorld's Matt Tyson has explored how htmx and Alpine.js can complement one another. His example concerns a particular combination of tools; the broader design decision is how to coordinate systems that can change the same interface. For an existing application, that means deciding which system controls each interaction and how the other participates.
Server-side support addresses several different integration tasks
Coordinating browser behavior leaves another part of integration on the server: preparing the templates and responses that htmx receives. Back-end frameworks generally remain neutral about front-end choices because favoring an option such as Bootstrap or Alpine.js can narrow their audience and tie client and server code more closely together. Optional integration support lets a framework help with a particular front end while remaining broadly usable.
Django and FastAPI offer one route through third-party htmx convenience libraries. These libraries help developers build htmx-powered interfaces while keeping the underlying frameworks suitable for other front-end approaches. Teams can add this supplementary integration support within an existing server framework.
Within that server work, partial-template rendering handles the narrower task of returning the portion of a page an htmx request needs. Jinja2, a templating language used with multiple back-end frameworks, supports this through the third-party Jinja2 Fragments extension. Developers render the relevant section of a template and return it in response to the fetch, handling this part of integration when preparing the response.
Litestar, in the Python ecosystem, provides native htmx support for another part of the work. It can render htmx-specific templates and automatically populate attributes, including those that specify target elements or event names. Because constructing htmx-aware markup serves a different purpose from rendering a template fragment, teams should assess these options against the specific server-side task they need to perform.
Key executive takeaways
- Stage htmx adoption: Existing JavaScript applications can introduce htmx while preserving current templates. Engineering managers can separate initial integration from later template changes and retain selected existing behavior.
- Keep declarative HTML as the target: HTML attributes express interactions close to the elements they control, reducing custom JavaScript. Application architects should identify which behaviors belong in templates as adoption progresses.
- Account for API coordination work: htmx APIs support event handling, content requests, and initialization of inserted content. Developers using this entry path remain responsible for coordinating those steps, which adds implementation work.
- Use query operations selectively: htmx 4's
q()operations simplify changes across groups of elements and work inside existing JavaScript. Front-end developers can introduce them incrementally where they simplify current code. - Assign ownership of reactive behavior: Systems that independently update the same interface can conflict. Front-end architects should define which system controls each interaction and check shared components, including those using Alpine.js with the compatibility extension.
- Match server support to the task: Convenience libraries, partial-template rendering, and native htmx support address different integration needs. Back-end teams should evaluate them against the specific work required, from returning page fragments to generating htmx-aware markup.
A project in mind?
Schedule a 30-minute meeting with us.
Senior experts helping you move faster across product, engineering, cloud & AI.


