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.
Calling a component binds its props. Rendering starts when it becomes a string or when you call .render():
greeting = Greeting(name="Ada")
html = greeting.render()Use Python expressions and control flow directly:
names: list[str]
---
for name in names:
<h1>Hello, {name}!</h1>print(Greeting(names=["Ada", "Lin"]))<h1>Hello, Ada!</h1><h1>Hello, Lin!</h1>Indentation defines Python blocks. Add an aligned end only when it makes a boundary clearer.
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 def ... -> Component to group related components in one file:
# app/components/forms.hyper
from hyper import Component
def Button(*, label: str) -> Component:
<button>{label}</button>
def Input(*, name: str) -> Component:
<input name={name} />A declarations-only file imports like a Python module:
from app.components.forms import Button, Input
print(Button(label="Save"))<button>Save</button>Import each name used by the source:
from hyper import ComponentSet the response type to HTML, then render 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().render()Pass stream=True to send each generated chunk:
from fastapi.responses import StreamingResponse
@app.get("/dashboard/stream")
def stream_dashboard():
return StreamingResponse(
Dashboard().render(stream=True),
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