Claude Code Plugins

Community-maintained marketplace

Feedback

Build interactive web apps in pure Python using FastHTML. Use when the user wants to prototype web apps in Python without JavaScript frameworks. FastHTML combines HTMX for interactivity, Starlette for routing, and Python "FastTags" for HTML generation. Ideal for rapid prototyping with real-time updates, forms, charts, and database-backed UIs.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name fasthtml
description Build interactive web apps in pure Python using FastHTML. Use when the user wants to prototype web apps in Python without JavaScript frameworks. FastHTML combines HTMX for interactivity, Starlette for routing, and Python "FastTags" for HTML generation. Ideal for rapid prototyping with real-time updates, forms, charts, and database-backed UIs.

FastHTML Dashboard Development

FastHTML creates server-rendered hypermedia apps in pure Python. It combines Starlette (routing), HTMX (interactivity), and FastTags (HTML generation).

Key principles:

  • Prefer Python over JS; never use React/Vue/Svelte
  • Use serve() to run (no if __name__ needed)
  • Return FT components or tuples from handlers
  • Use HTMX attributes for dynamic updates without page reloads

Minimal App

from fasthtml.common import *

app, rt = fast_app()

@rt
def index():
    return Titled("Dashboard", 
        P("Welcome to the dashboard"),
        A("View Data", hx_get=data, hx_target="#content"),
        Div(id="content"))

@rt
def data():
    return Div(H2("Data Loaded"), P("Dynamic content here"))

serve()

Run: python app.py → Access at localhost:5001

FastTags (FT Components)

HTML elements as Python functions. Positional args = children, keyword args = attributes.

# Basic elements
Div(P("Hello"), cls="container")  # cls → class
Input(id="email", type="email", placeholder="Email")
Button("Submit", type="submit", disabled=False)  # False omits attr

# Special attributes
Label("Name", _for="name")  # _for → for
Div(**{'@click': "handler()"})  # dict unpacking for special chars

# Common patterns
Form(method="post", action=handler)(
    Input(name="title", placeholder="Title"),
    Button("Save"))

HTMX Interactivity

Add hx_* attributes for dynamic behavior without JavaScript:

# GET request, swap inner HTML of #results
Button("Load", hx_get=search, hx_target="#results")

# POST form, swap response into #items after existing content
Form(hx_post=create, hx_target="#items", hx_swap="beforeend")(
    Input(name="item"), Button("Add"))

# Common hx_swap values: innerHTML (default), outerHTML, beforeend, afterbegin, delete
# Trigger on events: hx_trigger="click", "change", "keyup delay:500ms"

Dashboard Layout Pattern

from fasthtml.common import *

app, rt = fast_app()

def nav(): 
    return Nav(
        A("Dashboard", href=index), 
        A("Reports", href=reports),
        A("Settings", href=settings))

def layout(*content, title="Dashboard"):
    return Title(title), Container(nav(), Main(*content))

@rt
def index():
    return layout(
        H1("Overview"),
        Grid(
            Card(H3("Users"), P("1,234"), header=None, footer=A("View all", href=users)),
            Card(H3("Revenue"), P("$45,678")),
            Card(H3("Orders"), P("89"))),
        title="Dashboard")

Database-Backed Dashboard (Fastlite)

from fasthtml.common import *
from fastlite import database

db = database('dashboard.db')

class Metric: id:int; name:str; value:float; updated:str
metrics = db.create(Metric, transform=True)

app, rt = fast_app()

@rt
def index():
    return Titled("Metrics Dashboard",
        Div(id="metrics-list")(*[metric_card(m) for m in metrics()]),
        Form(hx_post=add_metric, hx_target="#metrics-list", hx_swap="beforeend")(
            Input(name="name", placeholder="Metric name"),
            Input(name="value", type="number", placeholder="Value"),
            Button("Add Metric")))

def metric_card(m):
    return Card(H4(m.name), P(f"{m.value}"), id=f"metric-{m.id}",
        footer=Button("Delete", hx_post=delete.to(id=m.id), 
                      hx_target=f"#metric-{m.id}", hx_swap="outerHTML"))

@rt
def add_metric(metric: Metric):
    return metric_card(metrics.insert(metric))

@rt
def delete(id: int):
    metrics.delete(id)
    return ""  # Empty response removes element

serve()

Charts with Plotly

app, rt = fast_app(hdrs=[Script(src="https://cdn.plot.ly/plotly-2.32.0.min.js")])

@rt
def index():
    data = [{"x": [1,2,3], "y": [4,1,2], "type": "scatter"}]
    return Titled("Chart Dashboard",
        Div(id="chart"),
        Script(f"Plotly.newPlot('chart', {data});"))

Real-Time Updates (SSE)

from fasthtml.common import *
import asyncio

hdrs = (Script(src="https://unpkg.com/htmx-ext-sse@2.2.3/sse.js"),)
app, rt = fast_app(hdrs=hdrs)
shutdown = signal_shutdown()

@rt
def index():
    return Titled("Live Dashboard",
        Div(hx_ext="sse", sse_connect="/stream", sse_swap="message", 
            hx_swap="innerHTML", id="live-data"))

async def generate():
    import random
    while not shutdown.is_set():
        yield sse_message(Div(f"Value: {random.randint(1,100)}"))
        await asyncio.sleep(1)

@rt
async def stream(): return EventStream(generate())

serve()

Forms and Data Binding

from dataclasses import dataclass

@dataclass
class Settings: theme:str; notifications:bool; limit:int

@rt
def settings():
    form = Form(hx_post=save_settings)(
        LabelInput("Theme", name="theme"),
        CheckboxX(id="notifications", label="Enable notifications"),
        LabelInput("Limit", name="limit", type="number"),
        Button("Save"))
    return Titled("Settings", fill_form(form, current_settings))

@rt
def save_settings(s: Settings):
    # s is auto-populated from form data
    save_to_db(s)
    return Div("Settings saved!", id="message")

MonsterUI for Rich Dashboards

For production dashboards with Tailwind styling, use MonsterUI:

from fasthtml.common import *
from monsterui.all import *

app, rt = fast_app(hdrs=Theme.blue.headers())

@rt
def index():
    return Titled("Dashboard",
        Grid(
            Card(H3("Metric 1"), P("$12,345", cls=TextPresets.bold_lg)),
            Card(H3("Metric 2"), P("1,234 users")),
            Card(H3("Metric 3"), P("98.5%")), cols=3),
        Card(
            CardHeader(H3("Recent Activity")),
            CardBody(
                Table(
                    Thead(Tr(Th("User"), Th("Action"), Th("Time"))),
                    Tbody(
                        Tr(Td("Alice"), Td("Login"), Td("2m ago")),
                        Tr(Td("Bob"), Td("Purchase"), Td("5m ago")))))))

serve()

MonsterUI provides: Card, Grid, Table, Button (with ButtonT.primary/destructive), Modal, NavBar, DivFullySpaced, DivCentered, LabelInput, LabelSelect, form components, and typography presets.

Common Patterns Reference

Pattern Code
Target by ID hx_target="#element-id"
Swap modes hx_swap="outerHTML", "beforeend", "afterbegin", "delete"
OOB update Div(id="x", hx_swap_oob="true") updates #x regardless of target
Route as href A("Link", href=handler) or hx_get=handler
With params handler.to(id=5)/handler?id=5
Confirm action hx_confirm="Are you sure?"
Loading indicator hx_indicator="#spinner"
Debounce input hx_trigger="keyup changed delay:500ms"

File Structure

my_dashboard/
├── main.py          # App entry point
├── data/
│   └── app.db       # SQLite database
└── static/          # Static files (auto-served)
    └── styles.css

Quick Reference

  • fast_app() - Create app with sensible defaults (includes Pico CSS)
  • fast_app(pico=False, hdrs=[...]) - Custom headers, no Pico
  • Titled(title, *children) - Page with Title + H1 + Container
  • Card(*c, header=, footer=) - Card component
  • Grid(*divs) - CSS grid layout
  • fill_form(form, obj) - Populate form from object
  • serve() - Run uvicorn with live reload

For comprehensive API details, see references/api-reference.md.