The fastest way to create HTML apps - a next-generation Python web framework for building fast, scalable web applications with minimal code
—
Built-in PicoCSS integration with enhanced components and styling utilities for rapid UI development.
FastHTML includes built-in support for PicoCSS, a minimal CSS framework that provides beautiful default styling with minimal markup.
picocss: str
"""PicoCSS CDN URL for including the framework."""
picolink: list
"""PicoCSS link and style elements for HTML head."""
picocondcss: str
"""PicoCSS conditional CDN URL with dark/light theme support."""
picocondlink: list
"""PicoCSS conditional link elements with theme switching."""
def set_pico_cls():
"""Set PicoCSS classes for Jupyter notebook display."""Pre-built components that leverage PicoCSS styling with additional FastHTML functionality.
def Card(*c, **kw):
"""
PicoCSS card component.
Creates an article element with header and footer sections,
styled as a card using PicoCSS classes.
Args:
*c: Card content (header, body, footer elements)
**kw: Additional attributes and styling
Returns:
Article element styled as card
"""
def Group(*c, **kw):
"""
PicoCSS form group.
Creates a form group container for organizing related
form elements with proper spacing and alignment.
Args:
*c: Form elements to group
**kw: Additional attributes
Returns:
Div element with form group styling
"""
def Search(*c, **kw):
"""
PicoCSS search input.
Creates a search input field with PicoCSS styling
and search-specific attributes.
Args:
*c: Content (usually none for input)
**kw: Input attributes (placeholder, name, etc.)
Returns:
Search input element
"""
def Grid(*c, **kw):
"""
PicoCSS grid container.
Creates a CSS grid container for responsive layouts
using PicoCSS grid system.
Args:
*c: Grid items
**kw: Grid attributes and styling
Returns:
Div element with grid styling
"""
def DialogX(*c, **kw):
"""
Enhanced dialog component.
Creates a modal dialog with PicoCSS styling
and enhanced functionality.
Args:
*c: Dialog content
**kw: Dialog attributes and options
Returns:
Dialog element with enhanced styling
"""
def Container(*c, **kw):
"""
PicoCSS container.
Creates a responsive container with proper max-width
and centering using PicoCSS classes.
Args:
*c: Container content
**kw: Container attributes
Returns:
Div element with container styling
"""
def PicoBusy(*c, **kw):
"""
PicoCSS busy indicator.
Creates a loading spinner or busy indicator
using PicoCSS styling.
Args:
*c: Content (usually none)
**kw: Styling attributes
Returns:
Element with busy indicator styling
"""Enhanced style and script elements with additional functionality for CSS and JavaScript management.
def Style(*c, **kw):
"""
Enhanced style tag.
Creates HTML style element with enhanced functionality
for CSS management and processing.
Args:
*c: CSS content
**kw: Style attributes
Returns:
Style element with enhanced functionality
"""
def StyleX(*c, **kw):
"""
Style tag with CSS file loading.
Creates style element that can load CSS from external
files or include inline styles.
Args:
*c: CSS content or file references
**kw: Style attributes and loading options
Returns:
Style element with file loading capability
"""
def Script(*c, **kw):
"""
Enhanced script tag.
Creates HTML script element with enhanced functionality
for JavaScript management.
Args:
*c: JavaScript content
**kw: Script attributes (src, type, etc.)
Returns:
Script element with enhanced functionality
"""
def ScriptX(*c, **kw):
"""
Script tag with file loading.
Creates script element that can load JavaScript from
external files or include inline scripts.
Args:
*c: JavaScript content or file references
**kw: Script attributes and loading options
Returns:
Script element with file loading capability
"""Utility functions for CSS processing and variable management.
def replace_css_vars(css_str: str, variables: dict) -> str:
"""
Replace CSS variables in string.
Substitutes CSS custom properties with actual values
for dynamic styling.
Args:
css_str: CSS string containing variables
variables: Dictionary of variable name/value pairs
Returns:
str: CSS string with variables replaced
"""
def loose_format(template: str, **kwargs) -> str:
"""
Flexible string formatting for CSS templates.
Args:
template: String template with placeholders
**kwargs: Values to substitute
Returns:
str: Formatted string
"""
def double_braces(s: str) -> str:
"""
Add double braces to string for template processing.
Args:
s: String to process
Returns:
str: String with double braces added
"""
def undouble_braces(s: str) -> str:
"""
Remove double braces from string.
Args:
s: String with double braces
Returns:
str: String with double braces removed
"""Functions for handling responsive design and theme switching.
def light_media() -> str:
"""
Light mode media query CSS.
Returns CSS media query for light color scheme preferences.
Returns:
str: CSS media query for light mode
"""
def dark_media() -> str:
"""
Dark mode media query CSS.
Returns CSS media query for dark color scheme preferences.
Returns:
str: CSS media query for dark mode
"""from fasthtml.common import *
app, rt = fast_app(pico=True) # Enable PicoCSS
@rt('/')
def homepage():
return Html(
Head(
Title("PicoCSS Demo"),
Meta(charset="utf-8"),
Meta(name="viewport", content="width=device-width, initial-scale=1"),
*picolink # Include PicoCSS stylesheets
),
Body(
Container(
H1("PicoCSS with FastHTML"),
P("This page uses PicoCSS for beautiful default styling."),
# PicoCSS automatically styles these elements
Button("Primary Button", cls="primary"),
Button("Secondary Button", cls="secondary"),
Button("Contrast Button", cls="contrast"),
# Form with PicoCSS styling
Form(
Group(
Label("Name:", for_="name"),
Input(type="text", name="name", placeholder="Enter your name")
),
Group(
Label("Email:", for_="email"),
Input(type="email", name="email", placeholder="Enter your email")
),
Button("Submit", type="submit")
)
)
)
)from fasthtml.common import *
app, rt = fast_app(pico=True)
@rt('/cards')
def card_demo():
return Titled("Card Components",
Container(
H1("PicoCSS Card Examples"),
Grid(
# Basic card
Card(
Header(H3("Basic Card")),
P("This is a basic card with header and content."),
Footer(Small("Card footer"))
),
# Card with image
Card(
Header(
Img(src="https://picsum.photos/300/150", alt="Card image")
),
H4("Image Card"),
P("A card with an image header."),
Footer(
Button("Action", cls="secondary")
)
),
# Feature card
Card(
Header(
H3("Feature Card"),
P("Premium feature", cls="badge")
),
Ul(
Li("Feature one"),
Li("Feature two"),
Li("Feature three")
),
Footer(
Button("Get Started", cls="primary"),
Button("Learn More", cls="secondary")
)
),
style="grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1rem;"
)
)
)from fasthtml.common import *
app, rt = fast_app(pico=True)
@rt('/forms')
def form_demo():
return Titled("Form Components",
Container(
H1("PicoCSS Form Styling"),
# Registration form
Card(
Header(H2("User Registration")),
Form(
Grid(
Group(
Label("First Name:", for_="first_name"),
Input(type="text", name="first_name", required=True)
),
Group(
Label("Last Name:", for_="last_name"),
Input(type="text", name="last_name", required=True)
)
),
Group(
Label("Email Address:", for_="email"),
Input(type="email", name="email", required=True)
),
Group(
Label("Password:", for_="password"),
Input(type="password", name="password", required=True)
),
Group(
Label("Country:", for_="country"),
Select(
Option("Select country...", value="", disabled=True, selected=True),
Option("United States", value="us"),
Option("Canada", value="ca"),
Option("United Kingdom", value="uk"),
Option("Other", value="other"),
name="country"
)
),
Group(
Label(
Input(type="checkbox", name="newsletter", value="yes"),
" Subscribe to newsletter"
)
),
Group(
Button("Create Account", type="submit", cls="primary"),
Button("Reset", type="reset", cls="secondary")
),
method="post",
action="/register"
)
),
# Search form
Card(
Header(H3("Search")),
Form(
Group(
Search(
name="query",
placeholder="Search products...",
hx_get="/search",
hx_target="#search-results",
hx_trigger="keyup changed delay:300ms"
),
Button("Search", type="submit", cls="primary")
),
Div(id="search-results")
)
)
)
)from fasthtml.common import *
app, rt = fast_app(pico=True)
@rt('/layout')
def layout_demo():
return Titled("Grid Layouts",
Container(
H1("Responsive Grid Examples"),
# Two-column layout
Section(
H2("Two Column Layout"),
Grid(
Div(
H3("Main Content"),
P("This is the main content area with more detailed information."),
P("Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
),
Div(
H3("Sidebar"),
P("This is sidebar content."),
Ul(
Li("Navigation item 1"),
Li("Navigation item 2"),
Li("Navigation item 3")
)
),
style="grid-template-columns: 2fr 1fr; gap: 2rem;"
)
),
# Three-column layout
Section(
H2("Three Column Layout"),
Grid(
Card(
Header(H4("Column 1")),
P("First column content."),
Footer(Button("Action 1"))
),
Card(
Header(H4("Column 2")),
P("Second column content."),
Footer(Button("Action 2"))
),
Card(
Header(H4("Column 3")),
P("Third column content."),
Footer(Button("Action 3"))
),
style="grid-template-columns: repeat(3, 1fr); gap: 1rem;"
)
),
# Responsive card grid
Section(
H2("Responsive Card Grid"),
Grid(
*[Card(
Header(H4(f"Card {i}")),
P(f"Content for card {i}"),
Footer(Button(f"Action {i}", cls="outline"))
) for i in range(1, 9)],
style="grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem;"
)
)
)
)from fasthtml.common import *
app, rt = fast_app(pico=True)
@rt('/custom-style')
def custom_styling():
# Custom CSS variables
custom_styles = Style("""
:root {
--primary-color: #ff6b6b;
--secondary-color: #4ecdc4;
--accent-color: #45b7d1;
}
.custom-card {
border-left: 4px solid var(--primary-color);
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}
.highlight-button {
background: var(--accent-color);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
transition: all 0.3s ease;
}
.highlight-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
@media (prefers-color-scheme: dark) {
.custom-card {
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
}
}
""")
return Html(
Head(
Title("Custom Styling"),
Meta(charset="utf-8"),
Meta(name="viewport", content="width=device-width, initial-scale=1"),
*picolink,
custom_styles
),
Body(
Container(
H1("Custom Styling Example"),
Card(
Header(H3("Custom Styled Card")),
P("This card uses custom CSS variables and styling."),
Footer(
Button("Custom Button", cls="highlight-button"),
Button("Regular Button", cls="outline")
),
cls="custom-card"
),
# Theme-aware styling
Section(
H2("Theme-Aware Components"),
P("These components adapt to light/dark mode preferences."),
Grid(
Card(
Header("🌞 Light Mode"),
P("Optimized for light backgrounds")
),
Card(
Header("🌙 Dark Mode"),
P("Automatically adapts to dark mode")
),
style="grid-template-columns: repeat(2, 1fr); gap: 1rem;"
)
)
)
)
)from fasthtml.common import *
app, rt = fast_app(pico=True)
@rt('/dynamic-style')
def dynamic_styling():
return Titled("Dynamic Styling",
Container(
H1("Dynamic Style Changes"),
# Color theme switcher
Card(
Header(H3("Theme Switcher")),
Group(
Button(
"Blue Theme",
hx_post="/set-theme/blue",
hx_target="#themed-content",
hx_swap="outerHTML"
),
Button(
"Green Theme",
hx_post="/set-theme/green",
hx_target="#themed-content",
hx_swap="outerHTML"
),
Button(
"Red Theme",
hx_post="/set-theme/red",
hx_target="#themed-content",
hx_swap="outerHTML"
)
),
Div(id="themed-content",
P("Click a theme button to change the styling dynamically.")
)
),
# Dynamic CSS classes
Card(
Header(H3("Dynamic Classes")),
Button(
"Toggle Highlight",
hx_post="/toggle-highlight",
hx_target="#highlight-demo",
hx_swap="outerHTML"
),
Div(id="highlight-demo",
P("This content can be dynamically highlighted.", cls="normal")
)
)
)
)
@rt('/set-theme/{theme}', methods=['POST'])
def set_theme(theme: str):
theme_styles = {
'blue': 'background: #e3f2fd; color: #0d47a1; border: 2px solid #2196f3;',
'green': 'background: #e8f5e8; color: #2e7d32; border: 2px solid #4caf50;',
'red': 'background: #ffebee; color: #c62828; border: 2px solid #f44336;'
}
style = theme_styles.get(theme, '')
return Div(
P(f"Theme changed to {theme.title()}!"),
P("This content now uses dynamic styling."),
style=style + ' padding: 1rem; border-radius: 0.5rem; margin-top: 1rem;',
id="themed-content"
)
@rt('/toggle-highlight', methods=['POST'])
def toggle_highlight():
return Div(
P("This content is now highlighted!",
style="background: yellow; padding: 1rem; border-radius: 0.5rem;"),
Button(
"Remove Highlight",
hx_post="/remove-highlight",
hx_target="#highlight-demo",
hx_swap="outerHTML"
),
id="highlight-demo"
)
@rt('/remove-highlight', methods=['POST'])
def remove_highlight():
return Div(
P("Highlight removed.", cls="normal"),
Button(
"Add Highlight",
hx_post="/toggle-highlight",
hx_target="#highlight-demo",
hx_swap="outerHTML"
),
id="highlight-demo"
)Install with Tessl CLI
npx tessl i tessl/pypi-python-fasthtml