Spaces:
Sleeping
Sleeping
# Concise reference | |
<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! --> | |
## About FastHTML | |
``` python | |
from fasthtml.common import * | |
``` | |
FastHTML is a python library which brings together Starlette, Uvicorn, | |
HTMX, and fastcore’s `FT` “FastTags” into a library for creating | |
server-rendered hypermedia applications. The | |
[`FastHTML`](https://www.fastht.ml/docs/api/core.html#fasthtml) class | |
itself inherits from `Starlette`, and adds decorator-based routing with | |
many additions, Beforeware, automatic `FT` to HTML rendering, and much | |
more. | |
Things to remember when writing FastHTML apps: | |
- *Not* compatible with FastAPI syntax; FastHTML is for HTML-first apps, | |
not API services (although it can implement APIs too) | |
- FastHTML includes support for Pico CSS and the fastlite sqlite | |
library, although using both are optional; sqlalchemy can be used | |
directly or via the fastsql library, and any CSS framework can be | |
used. MonsterUI is a richer FastHTML-first component framework with | |
similar capabilities to shadcn | |
- FastHTML is compatible with JS-native web components and any vanilla | |
JS library, but not with React, Vue, or Svelte | |
- Use [`serve()`](https://www.fastht.ml/docs/api/core.html#serve) for | |
running uvicorn (`if __name__ == "__main__"` is not needed since it’s | |
automatic) | |
- When a title is needed with a response, use | |
[`Titled`](https://www.fastht.ml/docs/api/xtend.html#titled); note | |
that that already wraps children in | |
[`Container`](https://www.fastht.ml/docs/api/pico.html#container), and | |
already includes both the meta title as well as the H1 element. | |
## Minimal App | |
The code examples here use fast.ai style: prefer ternary op, 1-line | |
docstring, minimize vertical space, etc. (Normally fast.ai style uses | |
few if any comments, but they’re added here as documentation.) | |
A minimal FastHTML app looks something like this: | |
``` python | |
# Meta-package with all key symbols from FastHTML and Starlette. Import it like this at the start of every FastHTML app. | |
from fasthtml.common import * | |
# The FastHTML app object and shortcut to `app.route` | |
app,rt = fast_app() | |
# Enums constrain the values accepted for a route parameter | |
name = str_enum('names', 'Alice', 'Bev', 'Charlie') | |
# Passing a path to `rt` is optional. If not passed (recommended), the function name is the route ('/foo') | |
# Both GET and POST HTTP methods are handled by default | |
# Type-annotated params are passed as query params (recommended) unless a path param is defined (which it isn't here) | |
@rt | |
def foo(nm: name): | |
# `Title` and `P` here are FastTags: direct m-expression mappings of HTML tags to Python functions with positional and named parameters. All standard HTML tags are included in the common wildcard import. | |
# When a tuple is returned, this returns concatenated HTML partials. HTMX by default will use a title HTML partial to set the current page name. HEAD tags (e.g. Meta, Link, etc) in the returned tuple are automatically placed in HEAD; everything else is placed in BODY. | |
# FastHTML will automatically return a complete HTML document with appropriate headers if a normal HTTP request is received. For an HTMX request, however, just the partials are returned. | |
return Title("FastHTML"), H1("My web app"), P(f"Hello, {name}!") | |
# By default `serve` runs uvicorn on port 5001. Never write `if __name__ == "__main__"` since `serve` checks it internally. | |
serve() | |
``` | |
To run this web app: | |
``` bash | |
python main.py # access via localhost:5001 | |
``` | |
## FastTags (aka FT Components or FTs) | |
FTs are m-expressions plus simple sugar. Positional params map to | |
children. Named parameters map to attributes. Aliases must be used for | |
Python reserved words. | |
``` python | |
tags = Title("FastHTML"), H1("My web app"), P(f"Let's do this!", cls="myclass") | |
tags | |
``` | |
(title(('FastHTML',),{}), | |
h1(('My web app',),{}), | |
p(("Let's do this!",),{'class': 'myclass'})) | |
This example shows key aspects of how FTs handle attributes: | |
``` python | |
Label( | |
"Choose an option", | |
Select( | |
Option("one", value="1", selected=True), # True renders just the attribute name | |
Option("two", value=2, selected=False), # Non-string values are converted to strings. False omits the attribute entirely | |
cls="selector", id="counter", # 'cls' becomes 'class' | |
**{'@click':"alert('Clicked');"}, # Dict unpacking for attributes with special chars | |
), | |
_for="counter", # '_for' becomes 'for' (can also use 'fr') | |
) | |
``` | |
Classes with `__ft__` defined are rendered using that method. | |
``` python | |
class FtTest: | |
def __ft__(self): return P('test') | |
to_xml(FtTest()) | |
``` | |
'<p>test</p>\n' | |
You can create new FTs by importing the new component from | |
`fasthtml.components`. If the FT doesn’t exist within that module, | |
FastHTML will create it. | |
``` python | |
from fasthtml.components import Some_never_before_used_tag | |
Some_never_before_used_tag() | |
``` | |
``` html | |
<some-never-before-used-tag></some-never-before-used-tag> | |
``` | |
FTs can be combined by defining them as a function. | |
``` python | |
def Hero(title, statement): return Div(H1(title),P(statement), cls="hero") | |
to_xml(Hero("Hello World", "This is a hero statement")) | |
``` | |
'<div class="hero">\n <h1>Hello World</h1>\n <p>This is a hero statement</p>\n</div>\n' | |
When handling a response, FastHTML will automatically render FTs using | |
the `to_xml` function. | |
``` python | |
to_xml(tags) | |
``` | |
'<title>FastHTML</title>\n<h1>My web app</h1>\n<p class="myclass">Let's do this!</p>\n' | |
## JS | |
The [`Script`](https://www.fastht.ml/docs/api/xtend.html#script) | |
function allows you to include JavaScript. You can use Python to | |
generate parts of your JS or JSON like this: | |
``` python | |
# In future snippets this import will not be shown, but is required | |
from fasthtml.common import * | |
app,rt = fast_app(hdrs=[Script(src="https://cdn.plot.ly/plotly-2.32.0.min.js")]) | |
# `index` is a special function name which maps to the `/` route. | |
@rt | |
def index(): | |
data = {'somedata':'fill me in…'} | |
# `Titled` returns a title tag and an h1 tag with the 1st param, with remaining params as children in a `Main` parent. | |
return Titled("Chart Demo", Div(id="myDiv"), Script(f"var data = {data}; Plotly.newPlot('myDiv', data);")) | |
# In future snippets `serve() will not be shown, but is required | |
serve() | |
``` | |
Prefer Python whenever possible over JS. Never use React or shadcn. | |
## fast_app hdrs | |
``` python | |
# In future snippets we'll skip showing the `fast_app` call if it has no params | |
app, rt = fast_app( | |
pico=False, # The Pico CSS framework is included by default, so pass `False` to disable it if needed. No other CSS frameworks are included. | |
# These are added to the `head` part of the page for non-HTMX requests. | |
hdrs=( | |
Link(rel='stylesheet', href='assets/normalize.min.css', type='text/css'), | |
Link(rel='stylesheet', href='assets/sakura.css', type='text/css'), | |
Style("p {color: red;}"), | |
# `MarkdownJS` and `HighlightJS` are available via concise functions | |
MarkdownJS(), HighlightJS(langs=['python', 'javascript', 'html', 'css']), | |
# by default, all standard static extensions are served statically from the web app dir, | |
# which can be modified using e.g `static_path='public'` | |
) | |
) | |
@rt | |
def index(req): return Titled("Markdown rendering example", | |
# This will be client-side rendered to HTML with highlight-js | |
Div("*hi* there",cls="marked"), | |
# This will be syntax highlighted | |
Pre(Code("def foo(): pass"))) | |
``` | |
## Responses | |
Routes can return various types: | |
1. FastTags or tuples of FastTags (automatically rendered to HTML) | |
2. Standard Starlette responses (used directly) | |
3. JSON-serializable types (returned as JSON in a plain text response) | |
``` python | |
@rt("/{fname:path}.{ext:static}") | |
async def serve_static_file(fname:str, ext:str): return FileResponse(f'public/{fname}.{ext}') | |
app, rt = fast_app(hdrs=(MarkdownJS(), HighlightJS(langs=['python', 'javascript']))) | |
@rt | |
def index(): | |
return Titled("Example", | |
Div("*markdown* here", cls="marked"), | |
Pre(Code("def foo(): pass"))) | |
``` | |
Route functions can be used in attributes like `href` or `action` and | |
will be converted to paths. Use `.to()` to generate paths with query | |
parameters. | |
``` python | |
@rt | |
def profile(email:str): return fill_form(profile_form, profiles[email]) | |
profile_form = Form(action=profile)( | |
Label("Email", Input(name="email")), | |
Button("Save", type="submit") | |
) | |
user_profile_path = profile.to(email="[email protected]") # '/profile?email=user%40example.com' | |
``` | |
``` python | |
from dataclasses import dataclass | |
app,rt = fast_app() | |
``` | |
When a route handler function is used as a fasttag attribute (such as | |
`href`, `hx_get`, or `action`) it is converted to that route’s path. | |
[`fill_form`](https://www.fastht.ml/docs/api/components.html#fill_form) | |
is used to copy an object’s matching attrs into matching-name form | |
fields. | |
``` python | |
@dataclass | |
class Profile: email:str; phone:str; age:int | |
email = '[email protected]' | |
profiles = {email: Profile(email=email, phone='123456789', age=5)} | |
@rt | |
def profile(email:str): return fill_form(profile_form, profiles[email]) | |
profile_form = Form(method="post", action=profile)( | |
Fieldset( | |
Label('Email', Input(name="email")), | |
Label("Phone", Input(name="phone")), | |
Label("Age", Input(name="age"))), | |
Button("Save", type="submit")) | |
``` | |
## Testing | |
We can use `TestClient` for testing. | |
``` python | |
from starlette.testclient import TestClient | |
``` | |
``` python | |
path = "/[email protected]" | |
client = TestClient(app) | |
htmx_req = {'HX-Request':'1'} | |
print(client.get(path, headers=htmx_req).text) | |
``` | |
<form enctype="multipart/form-data" method="post" action="/profile"><fieldset><label>Email <input name="email" value="[email protected]"> | |
</label><label>Phone <input name="phone" value="123456789"> | |
</label><label>Age <input name="age" value="5"> | |
</label></fieldset><button type="submit">Save</button></form> | |
## Form Handling and Data Binding | |
When a dataclass, namedtuple, etc. is used as a type annotation, the | |
form body will be unpacked into matching attribute names automatically. | |
``` python | |
@rt | |
def edit_profile(profile: Profile): | |
profiles[email]=profile | |
return RedirectResponse(url=path) | |
new_data = dict(email='[email protected]', phone='7654321', age=25) | |
print(client.post("/edit_profile", data=new_data, headers=htmx_req).text) | |
``` | |
<form enctype="multipart/form-data" method="post" action="/profile"><fieldset><label>Email <input name="email" value="[email protected]"> | |
</label><label>Phone <input name="phone" value="7654321"> | |
</label><label>Age <input name="age" value="25"> | |
</label></fieldset><button type="submit">Save</button></form> | |
## fasttag Rendering Rules | |
The general rules for rendering children inside tuples or fasttag | |
children are: - `__ft__` method will be called (for default components | |
like `P`, `H2`, etc. or if you define your own components) - If you pass | |
a string, it will be escaped - On other python objects, `str()` will be | |
called | |
If you want to include plain HTML tags directly into e.g. a `Div()` they | |
will get escaped by default (as a security measure to avoid code | |
injections). This can be avoided by using `Safe(...)`, e.g to show a | |
data frame use `Div(NotStr(df.to_html()))`. | |
## Exceptions | |
FastHTML allows customization of exception handlers. | |
``` python | |
def not_found(req, exc): return Titled("404: I don't exist!") | |
exception_handlers = {404: not_found} | |
app, rt = fast_app(exception_handlers=exception_handlers) | |
``` | |
## Cookies | |
We can set cookies using the | |
[`cookie()`](https://www.fastht.ml/docs/api/core.html#cookie) function. | |
``` python | |
@rt | |
def setcook(): return P(f'Set'), cookie('mycookie', 'foobar') | |
print(client.get('/setcook', headers=htmx_req).text) | |
``` | |
<p>Set</p> | |
``` python | |
@rt | |
def getcook(mycookie:str): return f'Got {mycookie}' | |
# If handlers return text instead of FTs, then a plaintext response is automatically created | |
print(client.get('/getcook').text) | |
``` | |
Got foobar | |
FastHTML provide access to Starlette’s request object automatically | |
using special `request` parameter name (or any prefix of that name). | |
``` python | |
@rt | |
def headers(req): return req.headers['host'] | |
``` | |
## Request and Session Objects | |
FastHTML provides access to Starlette’s session middleware automatically | |
using the special `session` parameter name (or any prefix of that name). | |
``` python | |
@rt | |
def profile(req, sess, user_id: int=None): | |
ip = req.client.host | |
sess['last_visit'] = datetime.now().isoformat() | |
visits = sess.setdefault('visit_count', 0) + 1 | |
sess['visit_count'] = visits | |
user = get_user(user_id or sess.get('user_id')) | |
return Titled(f"Profile: {user.name}", | |
P(f"Visits: {visits}"), | |
P(f"IP: {ip}"), | |
Button("Logout", hx_post=logout)) | |
``` | |
Handler functions can return the | |
[`HtmxResponseHeaders`](https://www.fastht.ml/docs/api/core.html#htmxresponseheaders) | |
object to set HTMX-specific response headers. | |
``` python | |
@rt | |
def htmlredirect(app): return HtmxResponseHeaders(location="http://example.org") | |
``` | |
## APIRouter | |
[`APIRouter`](https://www.fastht.ml/docs/api/core.html#apirouter) lets | |
you organize routes across multiple files in a FastHTML app. | |
``` python | |
# products.py | |
ar = APIRouter() | |
@ar | |
def details(pid: int): return f"Here are the product details for ID: {pid}" | |
@ar | |
def all_products(req): | |
return Div( | |
Div( | |
Button("Details",hx_get=details.to(pid=42),hx_target="#products_list",hx_swap="outerHTML",), | |
), id="products_list") | |
``` | |
``` python | |
# main.py | |
from products import ar,all_products | |
app, rt = fast_app() | |
ar.to_app(app) | |
@rt | |
def index(): | |
return Div( | |
"Products", | |
hx_get=all_products, hx_swap="outerHTML") | |
``` | |
## Toasts | |
Toasts can be of four types: | |
- info | |
- success | |
- warning | |
- error | |
Toasts require the use of the `setup_toasts()` function, plus every | |
handler needs: | |
- The session argument | |
- Must return FT components | |
``` python | |
setup_toasts(app) | |
@rt | |
def toasting(session): | |
add_toast(session, f"cooked", "info") | |
add_toast(session, f"ready", "success") | |
return Titled("toaster") | |
``` | |
`setup_toasts(duration)` allows you to specify how long a toast will be | |
visible before disappearing.10 seconds. | |
Authentication and authorization are handled with Beforeware, which | |
functions that run before the route handler is called. | |
## Auth | |
``` python | |
def user_auth_before(req, sess): | |
# `auth` key in the request scope is automatically provided to any handler which requests it and can not be injected | |
auth = req.scope['auth'] = sess.get('auth', None) | |
if not auth: return RedirectResponse('/login', status_code=303) | |
beforeware = Beforeware( | |
user_auth_before, | |
skip=[r'/favicon\.ico', r'/static/.*', r'.*\.css', r'.*\.js', '/login', '/'] | |
) | |
app, rt = fast_app(before=beforeware) | |
``` | |
## Server-Side Events (SSE) | |
FastHTML supports the HTMX SSE extension. | |
``` python | |
import random | |
hdrs=(Script(src="https://unpkg.com/[email protected]/sse.js"),) | |
app,rt = fast_app(hdrs=hdrs) | |
@rt | |
def index(): return Div(hx_ext="sse", sse_connect="/numstream", hx_swap="beforeend show:bottom", sse_swap="message") | |
# `signal_shutdown()` gets an event that is set on shutdown | |
shutdown_event = signal_shutdown() | |
async def number_generator(): | |
while not shutdown_event.is_set(): | |
data = Article(random.randint(1, 100)) | |
yield sse_message(data) | |
@rt | |
async def numstream(): return EventStream(number_generator()) | |
``` | |
## Websockets | |
FastHTML provides useful tools for HTMX’s websockets extension. | |
``` python | |
# These HTMX extensions are available through `exts`: | |
# head-support preload class-tools loading-states multi-swap path-deps remove-me ws chunked-transfer | |
app, rt = fast_app(exts='ws') | |
def mk_inp(): return Input(id='msg', autofocus=True) | |
@rt | |
async def index(request): | |
# `ws_send` tells HTMX to send a message to the nearest websocket based on the trigger for the form element | |
cts = Div( | |
Div(id='notifications'), | |
Form(mk_inp(), id='form', ws_send=True), | |
hx_ext='ws', ws_connect='/ws') | |
return Titled('Websocket Test', cts) | |
async def on_connect(send): await send(Div('Hello, you have connected', id="notifications")) | |
async def on_disconnect(ws): print('Disconnected!') | |
@app.ws('/ws', conn=on_connect, disconn=on_disconnect) | |
async def ws(msg:str, send): | |
# websocket hander returns/sends are treated as OOB swaps | |
await send(Div('Hello ' + msg, id="notifications")) | |
return Div('Goodbye ' + msg, id="notifications"), mk_inp() | |
``` | |
Sample chatbot that uses FastHTML’s | |
[`setup_ws`](https://www.fastht.ml/docs/api/core.html#setup_ws) | |
function: | |
``` py | |
app = FastHTML(exts='ws') | |
rt = app.route | |
msgs = [] | |
@rt('/') | |
def home(): | |
return Div(hx_ext='ws', ws_connect='/ws')( | |
Div(Ul(*[Li(m) for m in msgs], id='msg-list')), | |
Form(Input(id='msg'), id='form', ws_send=True) | |
) | |
async def ws(msg:str): | |
msgs.append(msg) | |
await send(Ul(*[Li(m) for m in msgs], id='msg-list')) | |
send = setup_ws(app, ws) | |
``` | |
### Single File Uploads | |
[`Form`](https://www.fastht.ml/docs/api/xtend.html#form) defaults to | |
“multipart/form-data”. A Starlette UploadFile is passed to the handler. | |
``` python | |
upload_dir = Path("filez") | |
@rt | |
def index(): | |
return ( | |
Form(hx_post=upload, hx_target="#result")( | |
Input(type="file", name="file"), | |
Button("Upload", type="submit")), | |
Div(id="result") | |
) | |
# Use `async` handlers where IO is used to avoid blocking other clients | |
@rt | |
async def upload(file: UploadFile): | |
filebuffer = await file.read() | |
(upload_dir / file.filename).write_bytes(filebuffer) | |
return P('Size: ', file.size) | |
``` | |
For multi-file, use `Input(..., multiple=True)`, and a type annotation | |
of `list[UploadFile]` in the handler. | |
## Fastlite | |
Fastlite and the MiniDataAPI specification it’s built on are a | |
CRUD-oriented API for working with SQLite. APSW and apswutils is used to | |
connect to SQLite, optimized for speed and clean error handling. | |
``` python | |
from fastlite import * | |
``` | |
``` python | |
db = database(':memory:') # or database('data/app.db') | |
``` | |
Tables are normally constructed with classes, field types are specified | |
as type hints. | |
``` python | |
class Book: isbn: str; title: str; pages: int; userid: int | |
# The transform arg instructs fastlite to change the db schema when fields change. | |
# Create only creates a table if the table doesn't exist. | |
books = db.create(Book, pk='isbn', transform=True) | |
class User: id: int; name: str; active: bool = True | |
# If no pk is provided, id is used as the primary key. | |
users = db.create(User, transform=True) | |
users | |
``` | |
<Table user (id, name, active)> | |
### Fastlite CRUD operations | |
Every operation in fastlite returns a full superset of dataclass | |
functionality. | |
``` python | |
user = users.insert(name='Alex',active=False) | |
user | |
``` | |
User(id=1, name='Alex', active=0) | |
``` python | |
# List all records | |
users() | |
``` | |
[User(id=1, name='Alex', active=0)] | |
``` python | |
# Limit, offset, and order results: | |
users(order_by='name', limit=2, offset=1) | |
# Filter on the results | |
users(where="name='Alex'") | |
# Placeholder for avoiding injection attacks | |
users("name=?", ('Alex',)) | |
# A single record by pk | |
users[user.id] | |
``` | |
User(id=1, name='Alex', active=0) | |
Test if a record exists by using `in` keyword on primary key: | |
``` python | |
1 in users | |
``` | |
True | |
Updates (which take a dict or a typed object) return the updated record. | |
``` python | |
user.name='Lauren' | |
user.active=True | |
users.update(user) | |
``` | |
User(id=1, name='Lauren', active=1) | |
`.xtra()` to automatically constrain queries, updates, and inserts from | |
there on: | |
``` python | |
users.xtra(active=True) | |
users() | |
``` | |
[User(id=1, name='Lauren', active=1)] | |
Deleting by pk: | |
``` python | |
users.delete(user.id) | |
``` | |
<Table user (id, name, active)> | |
NotFoundError is raised by pk `[]`, updates, and deletes. | |
``` python | |
try: users['Amy'] | |
except NotFoundError: print('User not found') | |
``` | |
User not found | |
## MonsterUI | |
MonsterUI is a shadcn-like component library for FastHTML. It adds the | |
Tailwind-based libraries FrankenUI and DaisyUI to FastHTML, as well as | |
Python’s mistletoe for Markdown, HighlightJS for code highlighting, and | |
Katex for latex support, following semantic HTML patterns when possible. | |
It is recommended for when you wish to go beyond the basics provided by | |
FastHTML’s built-in pico support. | |
A minimal app: | |
``` python | |
from fasthtml.common import * | |
from monsterui.all import * | |
app, rt = fast_app(hdrs=Theme.blue.headers(highlightjs=True)) # Use MonsterUI blue theme and highlight code in markdown | |
@rt | |
def index(): | |
socials = (('github','https://github.com/AnswerDotAI/MonsterUI'),) | |
return Titled("App", | |
Card( | |
P("App", cls=TextPresets.muted_sm), | |
# LabelInput, DivLAigned, and UkIconLink are non-semantic MonsterUI FT Components, | |
LabelInput('Email', type='email', required=True), | |
footer=DivLAligned(*[UkIconLink(icon,href=url) for icon,url in socials]))) | |
``` | |
MonsterUI recommendations: | |
- Use defaults as much as possible, for example | |
[`Container`](https://www.fastht.ml/docs/api/pico.html#container) in | |
monsterui already has defaults for margins | |
- Use `*T` for button styling consistency, for example | |
`cls=ButtonT.destructive` for a red delete button or | |
`cls=ButtonT.primary` for a CTA button | |
- Use `Label*` functions for forms as much as possible | |
(e.g. `LabelInput`, `LabelRange`) which creates and links both the | |
`FormLabel` and user input appropriately to avoid boiler plate. | |
Flex Layout Elements (such as `DivLAligned` and `DivFullySpaced`) can be | |
used to create layouts concisely | |
``` python | |
def TeamCard(name, role, location="Remote"): | |
icons = ("mail", "linkedin", "github") | |
return Card( | |
DivLAligned( | |
DiceBearAvatar(name, h=24, w=24), | |
Div(H3(name), P(role))), | |
footer=DivFullySpaced( | |
DivHStacked(UkIcon("map-pin", height=16), P(location)), | |
DivHStacked(*(UkIconLink(icon, height=16) for icon in icons)))) | |
``` | |
Forms are styled and spaced for you without significant additional | |
classes. | |
``` python | |
def MonsterForm(): | |
relationship = ["Parent",'Sibling', "Friend", "Spouse", "Significant Other", "Relative", "Child", "Other"] | |
return Div( | |
DivCentered( | |
H3("Emergency Contact Form"), | |
P("Please fill out the form completely", cls=TextPresets.muted_sm)), | |
Form( | |
Grid(LabelInput("Name",id='name'),LabelInput("Email", id='email')), | |
H3("Relationship to patient"), | |
Grid(*[LabelCheckboxX(o) for o in relationship], cols=4, cls='space-y-3'), | |
DivCentered(Button("Submit Form", cls=ButtonT.primary))), | |
cls='space-y-4') | |
``` | |
Text can be styled with markdown automatically with MonsterUI | |
```` python | |
render_md(""" | |
# My Document | |
> Important note here | |
+ List item with **bold** | |
+ Another with `code` | |
```python | |
def hello(): | |
print("world") | |
``` | |
""") | |
```` | |
'<div><h1 class="uk-h1 text-4xl font-bold mt-12 mb-6">My Document</h1>\n<blockquote class="uk-blockquote pl-4 border-l-4 border-primary italic mb-6">\n<p class="text-lg leading-relaxed mb-6">Important note here</p>\n</blockquote>\n<ul class="uk-list uk-list-bullet space-y-2 mb-6 ml-6 text-lg">\n<li class="leading-relaxed">List item with <strong>bold</strong></li>\n<li class="leading-relaxed">Another with <code class="uk-codespan px-1">code</code></li>\n</ul>\n<pre class="bg-base-200 rounded-lg p-4 mb-6"><code class="language-python uk-codespan px-1 uk-codespan px-1 block overflow-x-auto">def hello():\n print("world")\n</code></pre>\n</div>' | |
Or using semantic HTML: | |
``` python | |
def SemanticText(): | |
return Card( | |
H1("MonsterUI's Semantic Text"), | |
P( | |
Strong("MonsterUI"), " brings the power of semantic HTML to life with ", | |
Em("beautiful styling"), " and ", Mark("zero configuration"), "."), | |
Blockquote( | |
P("Write semantic HTML in pure Python, get modern styling for free."), | |
Cite("MonsterUI Team")), | |
footer=Small("Released February 2025"),) | |
``` | |