FastHTML provides default headers for every page, which are also fully customizable. This notebook explores how this works.
from fasthtml.common import * from fasthtml.jupyter import * from IPython.display import display,HTML
In 00_core there is a def_hdrs
function that returns the "default headers for a FastHTML app":
def_hdrs()
JS files for these libraries are included by default:
In that same 00_core notebook each of the default script headers above is defined with Script
, like:
htmxsrc = Script(src="https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js") htmxsrc
The meta headers are defined with Meta
like:
viewport = Meta(name="viewport", content="width=device-width, initial-scale=1, viewport-fit=cover") viewport
Later in 00_core when FastHTML
is defined, these are the relevant lines related to headers:
class FastHTML(Starlette):
def __init__(self, ..., hdrs=None)
...
if default_hdrs: hdrs = def_hdrs(htmx, surreal=surreal) + hdrs
hdrs += [Script(src=ext) for ext in exts.values()]
if IN_NOTEBOOK:
hdrs.append(iframe_scr)
from IPython.display import display,HTML
if nb_hdrs: display(HTML(to_xml(tuple(hdrs))))
middleware.append(cors_allow)
self.on_startup,self.on_shutdown,self.lifespan,self.hdrs,self.ftrs = on_startup,on_shutdown,lifespan,hdrs,ftrs
...
fast_app
If you instantiate FastHTML
via the fast_app convenience function, you can see those same default JS headers here, plus Pico and a sendmsg
JS function:
app,rt = fast_app()
app.hdrs
One of the things I love about FastHTML is how it's designed to work in Jupyter notebooks, and built that way from its core.
The more I use notebooks, the more I realize they're key to writing simple code that you understand thorougly inside and out. That is what makes code truly maintainable and long-lasting.
IN_NOTEBOOK
This is defined earlier in 00_core and appended to the in-notebook headers. Here iframes' height is auto-resized to their content, allowing FastHTML pages to be better shown in-notebook:
iframe_scr
If you set nb_hdrs=True
, the line display(HTML(to_xml(tuple(app.hdrs))))
will add the headers to the notebook.
Here we add a simple 1-file JS library, Tone.js:
Script(src="http://unpkg.com/tone")
We see the Tone.js header was added after the default FastHTML headers:
app,rt = fast_app(hdrs=(Script(src="https://unpkg.com/tone"),)) app.hdrs
This route handler has code from the Tone.js Hello World example:
@rt def index(): return Div( Script('const synth = new Tone.Synth().toDestination();synth.triggerAttackRelease("C4", "8n");'), P("This page should include the Tone.js header and play a tone") )
server = JupyUvi(app)
Uncomment and run this to play a tone:
# HTMX()
That works because the Tone.js header file was added.
MonsterUI has a great tutorial app showing how to customize FastHTML headers. It starts with adding the MonsterUI headers to fast_app
.
from monsterui.all import Theme, fast_app hdrs = Theme.blue.headers()
MonsterUI's theme headers include FrankenUI, Tailwind CSS, and theming code:
hdrs
MonsterUI is a little different because it comes with its own fast_app
that extends the one from FastHTML's fastapp.py. That is defined in monsterui/nbs/01_core.ipynb and used here:
app,rt = fast_app(hdrs=hdrs) app.hdrs
The code in MonsterUI for this is:
from fastcore.all import delegates import fasthtml.common as fh @delegates(fh.fast_app, but=['pico']) def fast_app(*args, pico=False, **kwargs): "Create a FastHTML or FastHTMLWithLiveReload app with `bg-background text-foreground` to bodykw for frankenui themes" if 'bodykw' not in kwargs: kwargs['bodykw'] = {} if 'class' not in kwargs['bodykw']: kwargs['bodykw']['class'] = '' kwargs['bodykw']['class'] = stringify((kwargs['bodykw']['class'],'bg-background text-foreground')) return fh.fast_app(*args, pico=pico, **kwargs)
app.bodykw