Setup and context — Why htmx in 2026?
While React, Next.js, and Vue.js continue to dominate frontend development, a quieter movement has been gaining serious momentum: htmx. In 2026, more and more developers are reaching for htmx as a pragmatic alternative to heavyweight JavaScript frameworks.
Built on the principle of Hypermedia As The Engine Of Application State (HATEOAS), htmx lets you build interactive UIs by simply adding HTML attributes — no custom JavaScript required. The server returns HTML fragments instead of JSON, and htmx inserts them directly into the DOM.
This approach pairs beautifully with AI chat applications. Since the server generates the response (including the AI's reply), there's no need for complex client-side state management. In this tutorial, we'll use Antigravity IDE to build a fully functional AI chat app with Python FastAPI, htmx, and the Gemini API.
Understanding the htmx Hypermedia Model
The core idea of htmx is deceptively simple: return HTML from your API, not JSON. In a traditional SPA, the server returns JSON and the client-side framework renders it. With htmx, the server returns ready-to-render HTML fragments, and the browser inserts them into the page.
<!-- A simple htmx-powered form -->
<form hx-post="/chat" hx-target="#chat-messages" hx-swap="beforeend">
<input type="text" name="message" placeholder="Ask the AI anything..." />
<button type="submit">Send</button>
</form>
<div id="chat-messages">
<!-- AI responses will be appended here -->
</div>Three attributes do all the work:
hx-post="/chat"— sends a POST request to/chaton form submithx-target="#chat-messages"— specifies where to insert the responsehx-swap="beforeend"— appends the response to the end of the target element
That's it. No useState, no useEffect, no event listeners — just HTML.
Setting Up Your Project in Antigravity IDE
Open a terminal in Antigravity IDE and run the following to initialize the project:
# Create the project directory
mkdir htmx-ai-chat && cd htmx-ai-chat
# Set up a Python virtual environment
python3 -m venv .venv
source .venv/bin/activate
# Install dependencies
pip install fastapi uvicorn jinja2 python-multipart google-generativeaiAntigravity IDE's autocomplete recognizes hx-* attributes in HTML files and provides intelligent suggestions. Once you create your requirements.txt, the editor also auto-suggests compatible package versions to prevent dependency conflicts.
Your project structure will look like this:
htmx-ai-chat/
├── main.py # FastAPI application
├── templates/
│ ├── base.html # Base template
│ └── chat.html # Chat UI
├── static/
│ └── style.css # Minimal styles
└── requirements.txt
Implementing the AI Chat with FastAPI + htmx
Here's the core main.py. The key design decision is having Antigravity's AI agent generate the business logic, so you can focus on the architecture.
# main.py
import os
from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import google.generativeai as genai
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
# Configure Gemini API
genai.configure(api_key=os.environ.get("GEMINI_API_KEY"))
model = genai.GenerativeModel("gemini-3-flash")
# In-memory chat history (use session management in production)
chat_history = []
@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
return templates.TemplateResponse("chat.html", {
"request": request,
"messages": chat_history
})
@app.post("/chat", response_class=HTMLResponse)
async def chat(request: Request, message: str = Form(...)):
"""Receives htmx POST request and returns an HTML fragment"""
# Add user message to history
chat_history.append({"role": "user", "content": message})
# Generate AI response via Gemini API
response = model.generate_content(message)
ai_reply = response.text
# Add AI response to history
chat_history.append({"role": "assistant", "content": ai_reply})
# Return an HTML fragment — no JavaScript needed on the client
return HTMLResponse(content=f"""
<div class="message user">
<strong>You:</strong> {message}
</div>
<div class="message assistant">
<strong>AI:</strong> {ai_reply}
</div>
""")<!-- templates/chat.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>AI Chat with htmx</title>
<!-- Load htmx from CDN -->
<script src="https://unpkg.com/htmx.org@2.0.0"></script>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="chat-container">
<h1>AI Chat (Gemini × htmx)</h1>
<!-- Chat history display area -->
<div id="chat-messages">
{% for msg in messages %}
<div class="message {{ msg.role }}">
<strong>{{ "You" if msg.role == "user" else "AI" }}:</strong>
{{ msg.content }}
</div>
{% endfor %}
</div>
<!-- Send form controlled by htmx -->
<form hx-post="/chat"
hx-target="#chat-messages"
hx-swap="beforeend"
hx-on::after-request="this.reset()">
<input type="text"
name="message"
placeholder="Ask the AI something..."
required />
<button type="submit"
hx-indicator="#loading">Send</button>
</form>
<!-- Loading indicator -->
<div id="loading" class="htmx-indicator">
AI is thinking...
</div>
</div>
</body>
</html>The hx-on::after-request="this.reset()" attribute clears the input field after each submission — again, no JavaScript required.
Integrating the Gemini API and Managing Environment Variables
Antigravity IDE handles environment variables through its project settings panel, which generates a .env file automatically. Referencing the key names in your AGENTS.md ensures Antigravity's agents always pull the correct values.
# .env file (auto-generated from Antigravity settings)
GEMINI_API_KEY=YOUR_GEMINI_API_KEY
# Start the development server
uvicorn main:app --reload --port 8000Antigravity IDE's inline error detection will flag missing environment variables before runtime, catching configuration issues early in development.
For more advanced Gemini API integration patterns, see Building Custom AI Tools with the Gemini API.
Adding Streaming Responses with Server-Sent Events
The Gemini API supports streaming. Pair it with htmx's SSE extension to display AI responses token by token — giving users that familiar "typing" effect without any client-side state management.
# Streaming endpoint using Server-Sent Events
from fastapi.responses import StreamingResponse
@app.get("/chat/stream")
async def chat_stream(message: str):
"""Returns Gemini streaming response as SSE"""
def generate():
response = model.generate_content(message, stream=True)
for chunk in response:
if chunk.text:
# SSE format: "data: <content>\n\n"
yield f"data: {chunk.text}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(generate(), media_type="text/event-stream")<!-- htmx SSE extension for streaming display -->
<script src="https://unpkg.com/htmx-ext-sse@2.2.1/sse.js"></script>
<div hx-ext="sse"
sse-connect="/chat/stream?message=Hello"
sse-swap="message">
<!-- Streaming text appears here token by token -->
</div>For more complex workflow orchestration across multiple AI steps, the Temporal Workflow Engine Integration Guide is an excellent next step.
Styling the Chat UI with Minimal CSS
One of htmx's underrated advantages is that it keeps your styling concerns entirely server-side and CSS-based — no CSS-in-JS, no Tailwind JIT, no build step needed. Here's a minimal stylesheet that makes the chat interface look clean and functional:
/* static/style.css */
.chat-container {
max-width: 720px;
margin: 2rem auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
#chat-messages {
min-height: 400px;
max-height: 600px;
overflow-y: auto;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
background: #fafafa;
}
.message {
padding: 0.75rem 1rem;
margin-bottom: 0.5rem;
border-radius: 8px;
line-height: 1.6;
}
.message.user {
background: #eff6ff;
border-left: 3px solid #3b82f6;
}
.message.assistant {
background: #f0fdf4;
border-left: 3px solid #22c55e;
}
/* htmx loading indicator */
.htmx-indicator {
display: none;
color: #6b7280;
font-style: italic;
padding: 0.5rem;
}
.htmx-request .htmx-indicator {
display: block;
}Antigravity IDE's CSS IntelliSense provides autocomplete for class names and color values, and its agent can generate a complete design system on request. Ask it "generate a glassmorphism theme for this chat UI" and it will produce the styles and update the template accordingly.
Deploying to Google Cloud Run
Once you're happy with the local version, deploy to Cloud Run for a production-grade serverless environment:
# Build and push the Docker image
gcloud builds submit --tag gcr.io/YOUR_PROJECT_ID/htmx-ai-chat
# Deploy to Cloud Run
gcloud run deploy htmx-ai-chat \
--image gcr.io/YOUR_PROJECT_ID/htmx-ai-chat \
--platform managed \
--region asia-northeast1 \
--allow-unauthenticated \
--set-env-vars GEMINI_API_KEY=YOUR_GEMINI_API_KEYAntigravity IDE's Cloudflare and GCP integration lets you trigger deployments directly from the editor. The agent can also generate the Dockerfile automatically — just ask: "Create a production Dockerfile for this FastAPI app."
For frontend animation polish on top of your htmx-powered pages, see Framer Motion Integration for Web Animations — it works seamlessly alongside htmx for page transitions and micro-interactions.
Summary
Combining htmx with Antigravity IDE gives you a surprisingly powerful stack for building AI-powered web apps — with minimal JavaScript overhead and maximum backend flexibility. Here's what we covered:
- Three
hx-*attributes replace entire React components for async UI updates - FastAPI + Jinja2 templates returning HTML fragments integrate seamlessly with htmx
- The SSE extension enables token-by-token streaming from the Gemini API
- Antigravity IDE's agent-assisted coding dramatically speeds up backend development
- Environment variable management and security checks come built into the IDE
htmx offers a refreshing counterpoint to SPA complexity — and it's particularly well-suited for AI applications where the server is already doing the heavy lifting. Give it a try alongside Antigravity IDE and see how quickly you can ship.