Back to Blog
2 min readTutorial

HTMX Common Patterns: Infinite Scroll, Modals, and More

Master common UI patterns with HTMX including infinite scroll, modals, search, and live updates

HTMX Common Patterns

Learn how to implement common UI patterns with HTMX.

Pattern 1: Infinite Scroll

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&quot;content&quot;</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Initial items --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">&quot;/items?page=2&quot;</span>
     <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">&quot;revealed&quot;</span>
     <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">&quot;afterend&quot;</span>
     <span class="hljs-attr">hx-target</span>=<span class="hljs-string">&quot;#content&quot;</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Loading more...<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

Pattern 2: Modal Dialog

<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">&quot;/modal/edit/1&quot;</span>
        <span class="hljs-attr">hx-target</span>=<span class="hljs-string">&quot;#modal&quot;</span>
        <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">&quot;innerHTML&quot;</span>&gt;</span>
    Edit
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&quot;modal&quot;</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;modal&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

<span class="hljs-comment">&lt;!-- Server returns --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;modal-content&quot;</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Edit Item<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">hx-put</span>=<span class="hljs-string">&quot;/items/1&quot;</span>&gt;</span>
        <span class="hljs-comment">&lt;!-- Form fields --&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

Pattern 3: Live Search

<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;search&quot;</span>
       <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;query&quot;</span>
       <span class="hljs-attr">hx-get</span>=<span class="hljs-string">&quot;/search&quot;</span>
       <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">&quot;keyup changed delay:500ms&quot;</span>
       <span class="hljs-attr">hx-target</span>=<span class="hljs-string">&quot;#results&quot;</span>
       <span class="hljs-attr">placeholder</span>=<span class="hljs-string">&quot;Search...&quot;</span> /&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&quot;results&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

Pattern 4: Inline Edit

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">hx-target</span>=<span class="hljs-string">&quot;this&quot;</span> <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">&quot;outerHTML&quot;</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{{ item.name }}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">&quot;/edit/{{ item.id }}&quot;</span>&gt;</span>Edit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

<span class="hljs-comment">&lt;!-- Server returns when editing --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">hx-put</span>=<span class="hljs-string">&quot;/update/{{ item.id }}&quot;</span>
      <span class="hljs-attr">hx-target</span>=<span class="hljs-string">&quot;this&quot;</span>
      <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">&quot;outerHTML&quot;</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;name&quot;</span> <span class="hljs-attr">value</span>=<span class="hljs-string">&quot;{{ item.name }}&quot;</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>Save<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">&quot;/cancel/{{ item.id }}&quot;</span>&gt;</span>Cancel<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>

Pattern 5: Form Validation

<span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">hx-post</span>=<span class="hljs-string">&quot;/submit&quot;</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;email&quot;</span>
           <span class="hljs-attr">hx-post</span>=<span class="hljs-string">&quot;/validate/email&quot;</span>
           <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">&quot;blur&quot;</span>
           <span class="hljs-attr">hx-target</span>=<span class="hljs-string">&quot;next .error&quot;</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;error&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>

Pattern 6: Polling

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">&quot;/status&quot;</span>
     <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">&quot;every 2s&quot;</span>
     <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">&quot;innerHTML&quot;</span>&gt;</span>
    Checking status...
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

Pattern 7: Dependent Dropdowns

<span class="hljs-tag">&lt;<span class="hljs-name">select</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;country&quot;</span>
        <span class="hljs-attr">hx-get</span>=<span class="hljs-string">&quot;/cities&quot;</span>
        <span class="hljs-attr">hx-target</span>=<span class="hljs-string">&quot;#city-select&quot;</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">&quot;us&quot;</span>&gt;</span>United States<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">&quot;ca&quot;</span>&gt;</span>Canada<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">select</span> <span class="hljs-attr">id</span>=<span class="hljs-string">&quot;city-select&quot;</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;city&quot;</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">option</span>&gt;</span>Select country first<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>

Pattern 8: Optimistic Updates

<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-post</span>=<span class="hljs-string">&quot;/like&quot;</span>
        <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">&quot;outerHTML&quot;</span>
        <span class="hljs-attr">hx-disabled-elt</span>=<span class="hljs-string">&quot;this&quot;</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;htmx-indicator&quot;</span>&gt;</span>Liking...<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    Like ({{ count }})
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

These patterns cover 90% of common UI needs. Simple, effective, no JavaScript required!

👨‍💻

Jordan Patel

Web Developer & Technology Enthusiast