Changing the Appearance of FastHTML Applications and Components#

How do you change the appearance of your FastHTML application? There are a few different ways. Be aware: they require you to know at least some CSS. We’ll cover the following:

  1. PicoCSS: the default CSS framework used by FastHTML

  2. Using a different CSS framework

  3. Applying inline styles

  4. Using your own custom CSS stylesheet

Understanding there approaches—and where to find more details on CSS—will give you a great deal of control ovet the appearance of your site.

CSS Basics#

Let’s start by answering a few basic questions about CSS and how it affects a webpage. We’re not going to provide any examples of CSS outside the context of FastHTML; for a more comprehensive overview, see the CSS Basics MDN Docs

  1. What is CSS? CSS (Cascading Style Sheets) is a style sheet language that lets you selectively apply style options to HTML elements. With CSS, you specify the types of HTML components you want to be affected, and how you want them displayed on the page.

  2. What is a CSS framework? A CSS framework like PicoCSS is an opinionated library of styles/templates that provides pre-defined classes and components to help you quickly build and style web pages.

  3. How is CSS applied? CSS can be applied in three ways: inline styles (directly in the HTML element), internal styles (within a <style> tag in the HTML document), and external styles (linking to an external CSS file).

PicoCSS#

PicoCSS is the default CSS framework used by FastHTML. It’s the reason the pages we put together in part 1 looked like they did and were not just black text on white backgrounds. Let’s see what happens when we disable Pico in the app configuration.

We will write the below file to app_2.py and run it with python app_2.py.

%%writefile app_2.py

from fasthtml.common import *

app, rt = fast_app(pico=False)


@app.get("/")
def hello():
    return (Title("Hello, World!"), Main(H1("Hello, World!"), cls="container"),
            Div(P("Goodbye, World!"), A("About", href="/about"), cls="container"))

@rt("/about")
def get():
    return(Titled("About this Site",
                  P("This is an example site built with FastHTML!"),
                  A("Return Home", href="/")))


serve()
Writing app_2.py

Previously, the site content was more centered on the page, the background was dark, and the text was light in a sans-serif font. Now we see a serif font, white background, black text, without padding on the sides (unless your browser applies any default styling). This shows the extent to which CSS is responsible for the appearance and layout of the site.

Applying a Different Framework#

Let’s apply a different framework. To do so, we leave pico=False and point to another stylesheet in the hdrs argument to fast_app.

%%writefile app_2.py

from fasthtml.common import *

app, rt = fast_app(
    pico=False,
    # list not tuple, tuple specified in docs
    hdrs = [
        Link(rel="stylesheet", href="https://unpkg.com/sakura.css/css/sakura.css", type='text/css')
    ]
)


@app.get("/")
def hello():
    return (Title("Hello, World!"), Main(H1("Hello, World!"), cls="container"),
            Div(P("Goodbye, World!"), A("About", href="/about"), cls="container"))

@rt("/about")
def get():
    return(Titled("About this Site",
                  P("This is an example site built with FastHTML!"),
                  A("Return Home", href="/")))


serve()
Overwriting app_2.py

And now we end up with a different theme, with centered text and a sans-serif font.

Applying a Custom Global Style#

Next, let’s remove Sakura and apply a custom global style to the site. We’ll do this with a Style tag in the hdrs argument to fast_app.

%%writefile app_2.py

from fasthtml.common import *

app, rt = fast_app(
    pico=False,
    # list not tuple, tuple specified in docs
    hdrs = [
        Style("""
            body {
                font-family: Arial, sans-serif;
                max-width: 800px;
                margin: 0 auto;
                padding: 0 20px;
                background-color: #f0f0f0;
            }
            a {
                color: red;
                text-decoration: none;
            }
            a:hover {
                text-decoration: underline;
            }
        """)
    ]
)


@app.get("/")
def hello():
    return (Title("Hello, World!"), Main(H1("Hello, World!"), cls="container"),
            Div(P("Goodbye, World!"), A("About", href="/about"), cls="container"))

@rt("/about")
def get():
    return(Titled("About this Site",
                  P("This is an example site built with FastHTML!"),
                  A("Return Home", href="/")))


serve()
Overwriting app_2.py

The Style tag is a global style tag that applies to the entire site. In FastHTML, it takes a string of CSS. We applied a number of style options to the site:

  • body { font-family: Arial, sans-serif; } sets the font family for the entire site to Arial, sans-serif.

  • max-width: 800px; sets the maximum width of the site to 800px.

  • margin: 0 auto; centers the site horizontally.

  • padding: 0 20px; adds 20px of padding to the left and right of the site.

  • background-color: #f0f0f0; sets the background color of the site to light gray.

  • a { color: red; } sets the color of all links to red.

  • a:hover { text-decoration: underline; } sets the text decoration of all links to underline when hovered over.

Styling Individual Components#

We can style individual components in much the same way we styled the site globally: components can take a style argument. Let’s go back to the basic application with picoCSS disabled and apply few component-specific styles.

%%writefile app_2.py

from fasthtml.common import *

app, rt = fast_app(pico=False)


@app.get("/")
def hello():
    return (
        Title("Hello, World!"),
        Main(
            H1("Hello, World!", style="color: blue; font-size: 24px;"),
            cls="container"
        ),
        Div(
            P("Goodbye, World!", style="color: green; font-style: italic;"),
            A("About", href="/about", style="color: red;"),
            cls="container"
        )
    )

@rt("/about")
def get():
    return Titled(
        "About this Site",
        P("This is an example site built with FastHTML!", style="color: purple; text-decoration: underline;"),
        A("Return Home", href="/", style="color: red;"),
        style="max-width: 800px; margin: 0 auto; padding: 0 20px; background-color: #f0f0f0;"
    )


serve()
Overwriting app_2.py

We applied a handful of different (mismatched, but that’s the point) styles to individual components with CSS strings passed to the style argument.

Using a Custom CSS Stylesheet#

Lastly, we can apply a custom CSS stylesheet to the site. To do so, we will:

  1. Write a CSS file that contains the styles we want to apply.

  2. Link to the CSS file in the hdrs argument to fast_app.

Let’s write a CSS file that implements the global style we applied earlier:

Style("""
    body {
        font-family: Arial, sans-serif;
        max-width: 800px;
        margin: 0 auto;
        padding: 0 20px;
        background-color: #f0f0f0;
    }
    a {
        color: red;
        text-decoration: none;
    }
    a:hover {
        text-decoration: underline;
    }
""")

The CSS file, which we will call style.css, should look like this:

body {
    font-family: Arial, sans-serif;
    max-width: 800px;
    margin: 0 auto;
    padding: 0 20px;
    background-color: #f0f0f0;
}
a {
    color: red;
    text-decoration: none;
}
a:hover {
    text-decoration: underline;
}
%%writefile style.css

body {
    font-family: Arial, sans-serif;
    max-width: 800px;
    margin: 0 auto;
    padding: 0 20px;
    background-color: #f0f0f0;
}
a {
    color: red;
    text-decoration: none;
}
a:hover {
    text-decoration: underline;
}
Writing style.css

Now, we can link to the CSS file in the hdrs argument to fast_app, just like we did with Sakura—except this time we’re pointing to our own custom stylesheet.

%%writefile app_2.py

from fasthtml.common import *

app, rt = fast_app(
    pico=False,
    hdrs=[
        Link(rel="stylesheet", href="style.css", type="text/css")
    ]
)

@app.get("/")
def hello():
    return (Title("Hello, World!"), Main(H1("Hello, World!"), cls="container"),
            Div(P("Goodbye, World!"), A("About", href="/about"), cls="container"))

@rt("/about")
def get():
    return(Titled("About this Site",
                  P("This is an example site built with FastHTML!"),
                  A("Return Home", href="/")))

serve()
Overwriting app_2.py

Summary#

Now you know a few different ways to change the appearance of your FastHTML application. You can apply a different CSS framework, apply a custom global style, style individual components, or apply a custom CSS stylesheet.

Spend some time playing with the different options and see how they affect the appearance of your site. This is a good resource for learning more about CSS and the different properties you can use.

I recommend the following exercises to build your familiarity with styling FastHTML applications:

  1. Search for and apply a different CSS framework to the site.

  2. Try combinations of different approaches, such as a custom stylesheet with some inidividual component styles.

  3. Pick a site you like and try to roughly approximate its style.

  4. Pick a few CSS properties from the MDN CSS Reference and try them out in your own FastHTML application.