Understanding FastHTML Headers

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

Default Headers

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:

How They're Defined

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

In the FastHTML app instance

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

In the app from 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

In-Notebook Behavior

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.

Customizing Headers: Adding a JS File

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.

Customizing Headers: Adding MonsterUI

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