htmx & Web Components: A Perfect Match for Frontend Development

A presentation at DevFest Dijon 2025 in December 2025 in 21000 Dijon, France by Horacio Gonzalez

Slide 1

Slide 1

</> htmx & Web Components A Perfect Match for Frontend Development

Slide 2

Slide 2

Horacio Gonzalez @LostInBrittany Espagnol Perdu en Bretagne

Slide 3

Slide 3

Create modern user interfaces with the simplicity and power of hypertext

Slide 4

Slide 4

Arbitrary Limitations of HTML ● Why can only <a> and <form> make HTTP(S) requests? ● Why can only click and submit events trigger them? ● Why are only GET and POST methods available? ● Why do <a> and <form> force a full page reload? ● Why so many arbitrary constraints in HTML?

Slide 5

Slide 5

Goal: Interactivity in Hypertext htmx extends HTML capabilities to: ● ● ● ● Perform AJAX requests Handle CSS transitions Work with WebSockets Process server-sent events All through declarative HTML attributes

Slide 6

Slide 6

But, what’s the point? It sounds nice and semantic, but what’s the real benefit?

Slide 7

Slide 7

A Quick Look Back A time when dinosaurs like me coded with Struts

Slide 8

Slide 8

Remember MVC? With its page-by-page navigation

Slide 9

Slide 9

The Golden Age of MVC Frameworks Generating HTML views

Slide 10

Slide 10

2005: The Arrival of AJAX The birth of Web 2.0

Slide 11

Slide 11

Web Pages Become Dynamic Apps Powered by JavaScript and jQuery

Slide 12

Slide 12

Shift to Single Page Applications (SPA)

Slide 13

Slide 13

Increasing Complexity The rise of JavaScript frameworks

Slide 14

Slide 14

Backend Becomes a REST API Serving JSON

Slide 15

Slide 15

We Gained Functionality But lost simplicity and semantics

Slide 16

Slide 16

Overkill for Many Applications Sometimes we just need a simple web page with a bit of interactivity

Slide 17

Slide 17

</> htmx Might Be the Right Solution It’s extended HTML ● Simplicity ● Semantics ● Interactivity

Slide 18

Slide 18

Too much theory, show us a demo! Examples, examples, examples! example-01.html <script src=”https://unpkg.com/htmx.org@2.0.2”></script> <!— have a button that POST on a click via AJAX and replace the content of #status div with the response —> <button hx-post=”/clicked” hx-target=”#status”> Click Me </button> <div id=”status”>Not yet clicked</div>

Slide 19

Slide 19

Too much theory, show us a demo! example-01.html https://github.com/LostInBrittany/introduction-to-htmx-and-lit

Slide 20

Slide 20

Sending POST on a button click ./html-examples/html-example-01.html example-01.html <script src=”https://unpkg.com/htmx.org@2.0.4”></script> <!— have a button that POST on a click via AJAX and replace the content of #status div with the response —> <button hx-post=”/clicked” hx-target=”#status”> Click Me </button> <div id=”status”>Not yet clicked</div>

Slide 21

Slide 21

GET, POST, PUT, DELETE… ./html-examples/html-example-02.html <script src=”https://unpkg.com/htmx.org@2.0.4”></script> <div hx-target=”#status”> <button hx-get=”/test-methods” inherit:target>Send GET</button> <button hx-post=”/test-methods” inherit:target>Send POST</button> <button hx-put=”/test-methods” inherit:target>Send PUT</button> <button hx-delete=”/test-methods” inherit:target>Send DELETE</button> </div> <div id=”status”>No request sent</div>

Slide 22

Slide 22

Using response to replace elements ./html-examples/html-example-03.html <script src=”https://unpkg.com/htmx.org@2.0.4”></script> <div id=”test-replace”> <button hx-get=”/test-replace/innerHTML”> If you click, this message will be replaced </button> <button hx-get=”/test-replace/outerHTML” hx-swap=”outerHTML”> If you click, this button will become a div </button> <button hx-get=”/test-replace/delete” hx-swap=”delete”> If you click, this button will disappear when the response is received </button> <button hx-get=”/test-replace/none” hx-swap=”none”> If you click, nothing changes, the response is ignored </button> </div>

Slide 23

Slide 23

Choosing when to send requests ./html-examples/html-example-04.html <script src=”https://unpkg.com/htmx.org@2.0.4”></script> <!— By default, AJAX requests are triggered by the “natural” event of an element: —> <div id=”test-triggers”> <button hx-get=”/trigger/natural” hx-target=”#status”> In a button the natural event is a click </button> <button hx-trigger=”mouseover” hx-get=”/trigger/mouseover” hx-target=”#status”> This button triggers on mouseover </button> <button hx-trigger=”mouseenter” hx-get=”/trigger/mouseenter” hx-target=”#status”> This button triggers on mouseenter </button> <button hx-trigger=”mouseleave” hx-get=”/trigger/mouseleave” hx-target=”#status”> This button triggers on mouseleave </button> </div> <div id=”status”>No AJAX request sent yet</div>

Slide 24

Slide 24

More triggering options ./html-examples/html-example-05.html <script src=”https://unpkg.com/htmx.org@2.0.4”></script> <!— By default, AJAX requests are triggered by the “natural” event of an element: —> <div id=”test-triggers”> <button hx-trigger=”every 5s” hx-get=”/trigger/5seconds” hx-target=”#status”> Sends request every 5 seconds, no event needed </button> <button hx-trigger=”click[ctrlKey]” hx-get=”/trigger/ctrlclick” hx-target=”#status”> Sends request on click while pressing Ctrl </button> <button hx-trigger=”click[ctrlKey] once” hx-get=”/trigger/ctrlclickonce” hx-target=”#status”> Sends request on the first click while pressing Ctrl </button> </div> <div id=”status”>No AJAX request sent yet</div>

Slide 25

Slide 25

A spinner to ease you wait ./html-examples/html-example-06.html <script src=”https://unpkg.com/htmx.org@2.0.4”></script> <!— By default, AJAX requests are triggered by the “natural” event of an element: —> <div id=”test-triggers”> <button hx-trigger=”every 5s” hx-get=”/trigger/5seconds” hx-target=”#status”> Sends request every 5 seconds, no event needed </button> <button hx-trigger=”click[ctrlKey]” hx-get=”/trigger/ctrlclick” hx-target=”#status”> Sends request on click while pressing Ctrl </button> <button hx-trigger=”click[ctrlKey] once” hx-get=”/trigger/ctrlclickonce” hx-target=”#status”> Sends request on the first click while pressing Ctrl </button> </div> <div id=”status”>No AJAX request sent yet</div>

Slide 26

Slide 26

Boosting elements ./html-examples/html-example-07.html

<script src=”https://unpkg.com/htmx.org@2.0.4”></script> <nav hx-boost=”true”> <ul> <li><a href=”./htmx-example-01.html”>Example 01</a></li> <li><a href=”./htmx-example-02.html”>Example 02</a></li> <li><a href=”./htmx-example-03.html”>Example 03</a></li> <li><a href=”/slow-request”>Slow Request (Raw Text)</a></li> </ul> </nav>

Slide 27

Slide 27

Extensions, extensions everywhere https://htmx.org/extensions/

Slide 28

Slide 28

Hypermedia Driven Applications (HDA) An HDA: ● Uses declarative, HTML-embedded syntax rather than imperative scripting to achieve better front-end interactivity ● Interacts with the server in terms of hypermedia (i.e. HTML) rather than a non-hypermedia format (e.g. JSON)

Slide 29

Slide 29

Hyperview: a companion of htmx https://hyperscript.org/

Slide 30

Slide 30

There is even a book: Hypermedia Systems https://hypermedia.systems/

Slide 31

Slide 31

Time for More Code! Let’s see a complete example

Slide 32

Slide 32

Too much theory, show us a demo! example-01.html https://github.com/LostInBrittany/introduction-to-htmx-and-lit

Slide 33

Slide 33

Let’s do a to-do list From Hello World to a To-do List

Slide 34

Slide 34

What the heck are web component? The 3 minutes context

Slide 35

Slide 35

Web Components Web standard W3C

Slide 36

Slide 36

Web Components Available in all modern browsers: Firefox, Safari, Chrome

Slide 37

Slide 37

Web Components Create your own HTML tags Encapsulating look and behavior

Slide 38

Slide 38

Web Components Fully interoperable With other web components, with any framework

Slide 39

Slide 39

Web Components CUSTOM ELEMENTS SHADOW DOM TEMPLATES

Slide 40

Slide 40

Custom Element To define your own HTML tag <body> … <script> window.customElements.define(‘my-element’, class extends HTMLElement {…}); </script> <my-element></my-element> </body>

Slide 41

Slide 41

Shadow DOM To encapsulate subtree and style in an element <button>Hello, world!</button> <script> var host = document.querySelector(‘button’); const shadowRoot = host.attachShadow({mode:’open’}); shadowRoot.textContent = ‘こんにちは、影の世界!’; </script>

Slide 42

Slide 42

Template To have clonable document template <template id=”mytemplate”> <img src=”” alt=”great image”> <div class=”comment”></div> </template> var t = document.querySelector(‘#mytemplate’); // Populate the src at runtime. t.content.querySelector(‘img’).src = ‘logo.png’; var clone = document.importNode(t.content, true); document.body.appendChild(clone);

Slide 43

Slide 43

But in fact, it’s just an element… ● ● ● ● Attributes Properties Methods Events

Slide 44

Slide 44

Simple. Fast. Web Components

Slide 45

Slide 45

Modern lightweight web components For the new web paradigm

Slide 46

Slide 46

LitElement import { LitElement, html } from ‘lit-element’; // Create your custom component class CustomGreeting extends LitElement { // Declare properties static get properties() { return { name: { type: String } }; } // Initialize properties constructor() { super(); this.name = ‘World’; } // Define a template render() { return html<p>Hello, ${this.name}!</p>; } } // Register the element with the browser customElements.define(‘custom-greeting’, CustomGreeting); Lightweight web-components using lit-html

Slide 47

Slide 47

Based on lit-html An efficient, expressive, extensible HTML templating library for JavaScript

Slide 48

Slide 48

Do you know tagged templates? function uppercaseExpression(strings, …expressionValues) { var finalString = ” for ( let i = 0; i < strings.length; i++ ) { if (i > 0) { finalString += expressionValues[i - 1].toUpperCase() } finalString += strings[i] } return finalString } const expressions = [ ‘Sophia Antipolis’, ‘RivieraDev’, ‘Thank you’]; console.log(uppercaseJe suis à ${expression[0]} pour ${expression[1]. $expression[2]! Little known functionality of template literals

Slide 49

Slide 49

lit-html Templates let myTemplate = (data) => html<h1>${data.title}</h1> <p>${data.body}</p>; Lazily rendered Generates a TemplateResult

Slide 50

Slide 50

It’s a bit like JSX, isn’t it? The good sides of JSX… but in the standard!

Slide 51

Slide 51

Too much theory, show us a demo! example-01.html https://github.com/LostInBrittany/introduction-to-htmx-and-lit

Slide 52

Slide 52

Custom Greeting example import { LitElement, html } from ‘lit-element’; // Create your custom component class CustomGreeting extends LitElement { // Declare properties static get properties() { return { name: { type: String } }; } // Initialize properties constructor() { super(); this.name = ‘World’; } // Define a template render() { return html<p>Hello, ${this.name}!</p>; } } // Register the element with the browser customElements.define(‘custom-greeting’, CustomGreeting); Lightweight web-components using lit-html

Slide 53

Slide 53

My Lit Counter example Let’s do an interactive counter

Slide 54

Slide 54

Lit & </> htmx Love at first <tag>

Slide 55

Slide 55

htmx for structure, Lit to encapsulate logic To htmx, Lit elements are just regular tags

Slide 56

Slide 56

Too much theory, show us a demo! example-01.html https://github.com/LostInBrittany/introduction-to-htmx-and-lit

Slide 57

Slide 57

That’s all, folks! Thank you all! r u o ey v a e l e s a Ple ack! b d e e f