Write type-safe HTML in .hyper files. Import components directly from Python.
uv add hyperhtmlRequires Python 3.10 or newer.
Create app/pages/Greeting.hyper:
<h1>Hello, World!</h1>
Import and call it:
from app.pages import Greeting
print(Greeting())<h1>Hello, World!</h1>The filename sets the component name.
Add typed props above ---:
name: str
---
<h1>Hello, {name}!</h1>
print(Greeting(name="Ada"))<h1>Hello, Ada!</h1>Props are keyword-only. Values are escaped by default.
Use Python expressions and control flow directly:
names: list[str]
---
for name in names:
<h1>Hello, {name}!</h1>
end
print(Greeting(names=["Ada", "Lin"]))<h1>Hello, Ada!</h1><h1>Hello, Lin!</h1>Close each indented block with end.
Create app/components/Card.hyper:
title: str
---
<article><h2>{title}</h2></article>
Use it from app/pages/Dashboard.hyper:
from app.components import Card
---
<{Card} title="Orders" />
from app.pages import Dashboard
print(Dashboard())<article><h2>Orders</h2></article>Place {...} where caller content belongs:
title: str
---
<article>
<h2>{title}</h2>
<div>{...}</div>
</article>
Pass content between component tags:
from app.components import Card
---
<{Card} title="Orders">
<p>3 open orders</p>
</{Card}>
<article><h2>Orders</h2><div><p>3 open orders</p></div></article>Use a named slot when content belongs in a specific place:
title: str
---
<article>
<h2>{title}</h2>
<div>{...}</div>
<footer>{...actions}</footer>
</article>
Mark the element that fills it:
from app.components import Card
---
<{Card} title="Orders">
<p>3 open orders</p>
<button {...actions}>View orders</button>
</{Card}>
<article><h2>Orders</h2><div><p>3 open orders</p></div><footer><button>View orders</button></footer></article>Use component to group related components in one file:
# app/components/forms.hyper
component Button(*, label: str):
<button>{label}</button>
end
component Input(*, name: str):
<input name={name} />
end
A declarations-only file imports like a Python module:
from app.components.forms import Button, Input
print(Button(label="Save"))<button>Save</button>Set the response type to HTML, then return a component:
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from app.pages import Dashboard
app = FastAPI(default_response_class=HTMLResponse)
@app.get("/dashboard")
def dashboard():
return Dashboard()Use .stream() to send each generated chunk:
from fastapi.responses import StreamingResponse
@app.get("/dashboard/stream")
def stream_dashboard():
return StreamingResponse(
Dashboard.stream(),
media_type="text/html",
)See Integrations for Django, Jinja, Flask, Litestar, and Sanic.
- JetBrains: syntax highlighting, Python language injection, and compiler diagnostics
- TextMate and VS Code: syntax highlighting