ANTIGRAVITY LABJP
Articles/Integrations
Integrations/2026-04-05Intermediate

Antigravity × htmx: Building an HTML-Driven AI Chat App — Zero JavaScript, Full Interactivity

Build an AI chat app with Antigravity IDE, htmx, FastAPI, and Gemini API — zero JavaScript. A hands-on tutorial for hypermedia-driven development with Python.

htmxhypermediaFastAPIGemini API4Python14AI chatreal-time3

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 /chat on form submit
  • hx-target="#chat-messages" — specifies where to insert the response
  • hx-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-generativeai

Antigravity 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 8000

Antigravity 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_KEY

Antigravity 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.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Integrations2026-05-14
Antigravity × Gemma 4 API Implementation Guide — Build from Zero with Python & TypeScript
Call Gemma 4 API from Antigravity IDE. Python & TypeScript code examples, streaming, error handling, and Next.js integration — production-ready guide.
Integrations2026-04-24
Antigravity × Gemini File API: A Production Guide to Feeding Long-Form Media (Video, Audio, PDF) into Your Agents
Feed hour-long videos, podcasts, and book-length PDFs into your Antigravity agents with the Gemini File API. A practical, production-oriented pipeline with timestamped highlight extraction, idempotent uploads, cost accounting, and failure recovery.
Agents & Manager2026-06-12
Running Gemini's Managed Agents API: Where Cloud Execution Ends and My Local Agents Begin
A hands-on record of launching Gemini's Managed Agents (public preview) from Python — polling, artifact retrieval, and a cost guard — plus five criteria I use to decide what stays on my local CLI agents.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →