head
+from lxml import html as lx
+from pprint import pprint
From 58c833614b7cd24f7f55548d07600d4a28110e15 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 13 Oct 2024 21:22:07 +0000 Subject: [PATCH] deploy: db9f5c964cda0c96e0b7ac683b4d25811fa46b36 --- .nojekyll | 0 CNAME | 1 + api/README.txt | 2 + api/cli.html | 841 +++ api/cli.html.md | 53 + api/components.html | 1115 ++++ api/components.html.md | 319 ++ api/core.html | 1984 +++++++ api/core.html.md | 1264 +++++ api/fastapp.html | 1097 ++++ api/fastapp.html.md | 273 + api/js.html | 1103 ++++ api/js.html.md | 305 ++ api/jupyter.html | 1003 ++++ api/jupyter.html.md | 205 + api/oauth.html | 1020 ++++ api/oauth.html.md | 230 + api/pico.html | 1074 ++++ api/pico.html.md | 241 + api/svg.html | 1391 +++++ api/svg.html.md | 639 +++ api/xtend.html | 1227 +++++ api/xtend.html.md | 447 ++ apilist.txt | 444 ++ explains/explaining_xt_components.html | 1001 ++++ explains/explaining_xt_components.html.md | 215 + explains/faq.html | 906 ++++ explains/faq.html.md | 133 + explains/imgs/gh-oauth.png | Bin 0 -> 122885 bytes explains/minidataapi.html | 1376 +++++ explains/minidataapi.html.md | 666 +++ explains/oauth.html | 1031 ++++ explains/oauth.html.md | 360 ++ explains/routes.html | 1001 ++++ explains/routes.html.md | 217 + explains/websockets.html | 937 ++++ explains/websockets.html.md | 173 + favicon.ico | Bin 0 -> 15086 bytes index.html | 920 ++++ index.html.md | 152 + listings.json | 11 + llms-ctx-full.txt | 4637 +++++++++++++++++ llms-ctx.txt | 3041 +++++++++++ llms.txt | 33 + logo.svg | 32 + ref/defining_xt_component.html | 983 ++++ ref/defining_xt_component.md | 202 + ref/handlers.html | 1609 ++++++ ref/handlers.html.md | 1131 ++++ ref/live_reload.html | 864 +++ ref/live_reload.html.md | 61 + robots.txt | 1 + search.json | 1433 +++++ site_libs/bootstrap/bootstrap-icons.css | 2078 ++++++++ site_libs/bootstrap/bootstrap-icons.woff | Bin 0 -> 176200 bytes site_libs/bootstrap/bootstrap.min.css | 12 + site_libs/bootstrap/bootstrap.min.js | 7 + site_libs/clipboard/clipboard.min.js | 7 + site_libs/quarto-html/anchor.min.js | 9 + site_libs/quarto-html/popper.min.js | 6 + .../quarto-syntax-highlighting.css | 205 + site_libs/quarto-html/quarto.js | 908 ++++ site_libs/quarto-html/tippy.css | 1 + site_libs/quarto-html/tippy.umd.min.js | 2 + site_libs/quarto-listing/list.min.js | 2 + site_libs/quarto-listing/quarto-listing.js | 243 + site_libs/quarto-nav/headroom.min.js | 7 + site_libs/quarto-nav/quarto-nav.js | 325 ++ site_libs/quarto-search/autocomplete.umd.js | 3 + site_libs/quarto-search/fuse.min.js | 9 + site_libs/quarto-search/quarto-search.js | 1290 +++++ sitemap.xml | 107 + styles.css | 47 + tutorials/by_example.html | 1904 +++++++ tutorials/by_example.html.md | 1558 ++++++ .../figure-commonmark/cell-101-1-image.png | Bin 0 -> 93632 bytes .../figure-commonmark/cell-53-1-image.png | Bin 0 -> 45599 bytes .../figure-commonmark/cell-58-1-image.png | Bin 0 -> 1411382 bytes .../figure-html/cell-101-1-image.png | Bin 0 -> 93632 bytes .../figure-html/cell-53-1-image.png | Bin 0 -> 45599 bytes .../figure-html/cell-58-1-image.png | Bin 0 -> 1411382 bytes tutorials/e2e.html | 1251 +++++ tutorials/e2e.html.md | 501 ++ tutorials/imgs/quickdraw.png | Bin 0 -> 67243 bytes tutorials/index.html | 903 ++++ tutorials/index.md | 5 + tutorials/jupyter_and_fasthtml.html | 884 ++++ tutorials/jupyter_and_fasthtml.html.md | 97 + .../quickstart-fasthtml.png | Bin 0 -> 13843 bytes tutorials/quickstart_for_web_devs.html | 1919 +++++++ tutorials/quickstart_for_web_devs.html.md | 1219 +++++ unpublished/tutorial_for_web_devs.html | 1085 ++++ unpublished/tutorial_for_web_devs.html.md | 318 ++ .../web-dev-tut/random-list-letters.png | Bin 0 -> 18818 bytes 94 files changed, 56316 insertions(+) create mode 100644 .nojekyll create mode 100644 CNAME create mode 100644 api/README.txt create mode 100644 api/cli.html create mode 100644 api/cli.html.md create mode 100644 api/components.html create mode 100644 api/components.html.md create mode 100644 api/core.html create mode 100644 api/core.html.md create mode 100644 api/fastapp.html create mode 100644 api/fastapp.html.md create mode 100644 api/js.html create mode 100644 api/js.html.md create mode 100644 api/jupyter.html create mode 100644 api/jupyter.html.md create mode 100644 api/oauth.html create mode 100644 api/oauth.html.md create mode 100644 api/pico.html create mode 100644 api/pico.html.md create mode 100644 api/svg.html create mode 100644 api/svg.html.md create mode 100644 api/xtend.html create mode 100644 api/xtend.html.md create mode 100644 apilist.txt create mode 100644 explains/explaining_xt_components.html create mode 100644 explains/explaining_xt_components.html.md create mode 100644 explains/faq.html create mode 100644 explains/faq.html.md create mode 100644 explains/imgs/gh-oauth.png create mode 100644 explains/minidataapi.html create mode 100644 explains/minidataapi.html.md create mode 100644 explains/oauth.html create mode 100644 explains/oauth.html.md create mode 100644 explains/routes.html create mode 100644 explains/routes.html.md create mode 100644 explains/websockets.html create mode 100644 explains/websockets.html.md create mode 100644 favicon.ico create mode 100644 index.html create mode 100644 index.html.md create mode 100644 listings.json create mode 100644 llms-ctx-full.txt create mode 100644 llms-ctx.txt create mode 100644 llms.txt create mode 100644 logo.svg create mode 100644 ref/defining_xt_component.html create mode 100644 ref/defining_xt_component.md create mode 100644 ref/handlers.html create mode 100644 ref/handlers.html.md create mode 100644 ref/live_reload.html create mode 100644 ref/live_reload.html.md create mode 100644 robots.txt create mode 100644 search.json create mode 100644 site_libs/bootstrap/bootstrap-icons.css create mode 100644 site_libs/bootstrap/bootstrap-icons.woff create mode 100644 site_libs/bootstrap/bootstrap.min.css create mode 100644 site_libs/bootstrap/bootstrap.min.js create mode 100644 site_libs/clipboard/clipboard.min.js create mode 100644 site_libs/quarto-html/anchor.min.js create mode 100644 site_libs/quarto-html/popper.min.js create mode 100644 site_libs/quarto-html/quarto-syntax-highlighting.css create mode 100644 site_libs/quarto-html/quarto.js create mode 100644 site_libs/quarto-html/tippy.css create mode 100644 site_libs/quarto-html/tippy.umd.min.js create mode 100644 site_libs/quarto-listing/list.min.js create mode 100644 site_libs/quarto-listing/quarto-listing.js create mode 100644 site_libs/quarto-nav/headroom.min.js create mode 100644 site_libs/quarto-nav/quarto-nav.js create mode 100644 site_libs/quarto-search/autocomplete.umd.js create mode 100644 site_libs/quarto-search/fuse.min.js create mode 100644 site_libs/quarto-search/quarto-search.js create mode 100644 sitemap.xml create mode 100644 styles.css create mode 100644 tutorials/by_example.html create mode 100644 tutorials/by_example.html.md create mode 100644 tutorials/by_example_files/figure-commonmark/cell-101-1-image.png create mode 100644 tutorials/by_example_files/figure-commonmark/cell-53-1-image.png create mode 100644 tutorials/by_example_files/figure-commonmark/cell-58-1-image.png create mode 100644 tutorials/by_example_files/figure-html/cell-101-1-image.png create mode 100644 tutorials/by_example_files/figure-html/cell-53-1-image.png create mode 100644 tutorials/by_example_files/figure-html/cell-58-1-image.png create mode 100644 tutorials/e2e.html create mode 100644 tutorials/e2e.html.md create mode 100644 tutorials/imgs/quickdraw.png create mode 100644 tutorials/index.html create mode 100644 tutorials/index.md create mode 100644 tutorials/jupyter_and_fasthtml.html create mode 100644 tutorials/jupyter_and_fasthtml.html.md create mode 100644 tutorials/quickstart-web-dev/quickstart-fasthtml.png create mode 100644 tutorials/quickstart_for_web_devs.html create mode 100644 tutorials/quickstart_for_web_devs.html.md create mode 100644 unpublished/tutorial_for_web_devs.html create mode 100644 unpublished/tutorial_for_web_devs.html.md create mode 100644 unpublished/web-dev-tut/random-list-letters.png diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/CNAME b/CNAME new file mode 100644 index 00000000..fe2e8862 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +docs.fastht.ml diff --git a/api/README.txt b/api/README.txt new file mode 100644 index 00000000..7a38fd9b --- /dev/null +++ b/api/README.txt @@ -0,0 +1,2 @@ +These are the source notebooks for FastHTML. + diff --git a/api/cli.html b/api/cli.html new file mode 100644 index 00000000..052bd86c --- /dev/null +++ b/api/cli.html @@ -0,0 +1,841 @@ + +
+ + + + + + + ++ | Type | +Default | +Details | +
---|---|---|---|
name | +str | ++ | The project name to deploy | +
mount | +bool_arg | +True | +Create a mounted volume at /app/data? | +
+FastHTML is Fast
+ +``` python +# Called without the `show()` function, the raw HTML is displayed +sentence +``` + +``` html ++FastHTML is Fast
+``` + +------------------------------------------------------------------------ + +source + +### attrmap_x + +> attrmap_x (o) + +------------------------------------------------------------------------ + +source + +### ft_html + +> ft_html (tag:str, *c, id=None, cls=None, title=None, style=None, +> attrmap=None, valmap=None, ft_cls=None, auto_id=None, **kwargs) + +------------------------------------------------------------------------ + +source + +### ft_hx + +> ft_hx (tag:str, *c, target_id=None, hx_vals=None, id=None, cls=None, +> title=None, style=None, accesskey=None, contenteditable=None, +> dir=None, draggable=None, enterkeyhint=None, hidden=None, +> inert=None, inputmode=None, lang=None, popover=None, +> spellcheck=None, tabindex=None, translate=None, hx_get=None, +> hx_post=None, hx_put=None, hx_delete=None, hx_patch=None, +> hx_trigger=None, hx_target=None, hx_swap=None, hx_swap_oob=None, +> hx_include=None, hx_select=None, hx_select_oob=None, +> hx_indicator=None, hx_push_url=None, hx_confirm=None, +> hx_disable=None, hx_replace_url=None, hx_disabled_elt=None, +> hx_ext=None, hx_headers=None, hx_history=None, +> hx_history_elt=None, hx_inherit=None, hx_params=None, +> hx_preserve=None, hx_prompt=None, hx_request=None, hx_sync=None, +> hx_validate=None, **kwargs) + +``` python +ft_html('a', _at_click_dot_away=1) +``` + +``` html + +``` + +``` python +ft_html('a', **{'@click.away':1}) +``` + +``` html + +``` + +``` python +ft_html('a', {'@click.away':1}) +``` + +``` html + +``` + +``` python +ft_hx('a', hx_vals={'a':1}) +``` + +``` html + +``` + +------------------------------------------------------------------------ + +source + +### File + +> File (fname) + +*Use the unescaped text in file `fname` directly* + +For tags that have a `name` attribute, it will be set to the value of +`id` if not provided explicitly: + +``` python +Form(Button(target_id='foo', id='btn'), + hx_post='/', target_id='tgt', id='frm') +``` + +``` html + +``` + +------------------------------------------------------------------------ + +source + +### fill_form + +> fill_form (form:fastcore.xml.FT, obj) + +*Fills named items in `form` using attributes in `obj`* + +``` python +@dataclass +class TodoItem: + title:str; id:int; done:bool; details:str; opt:str='a' + +todo = TodoItem(id=2, title="Profit", done=True, details="Details", opt='b') +check = Label(Input(type="checkbox", cls="checkboxer", name="done", data_foo="bar"), "Done", cls='px-2') +form = Form(Fieldset(Input(cls="char", id="title", value="a"), check, Input(type="hidden", id="id"), + Select(Option(value='a'), Option(value='b'), name='opt'), + Textarea(id='details'), Button("Save"), + name="stuff")) +form = fill_form(form, todo) +assert '' in to_xml(form) +form +``` + +``` html + +``` + +------------------------------------------------------------------------ + +source + +### fill_dataclass + +> fill_dataclass (src, dest) + +*Modifies dataclass in-place and returns it* + +``` python +nt = TodoItem('', 0, False, '') +fill_dataclass(todo, nt) +nt +``` + + TodoItem(title='Profit', id=2, done=True, details='Details', opt='b') + +------------------------------------------------------------------------ + +source + +### find_inputs + +> find_inputs (e, tags='input', **kw) + +*Recursively find all elements in `e` with `tags` and attrs matching +`kw`* + +``` python +inps = find_inputs(form, id='title') +test_eq(len(inps), 1) +inps +``` + + [input((),{'value': 'Profit', 'id': 'title', 'class': 'char', 'name': 'title'})] + +You can also use lxml for more sophisticated searching: + +``` python +elem = lx.fromstring(to_xml(form)) +test_eq(elem.xpath("//input[@id='title']/@value"), ['Profit']) +``` + +------------------------------------------------------------------------ + +source + +### **getattr** + +> __getattr__ (tag) + +------------------------------------------------------------------------ + +source + +### html2ft + +> html2ft (html, attr1st=False) + +*Convert HTML to an `ft` expression* + +``` python +h = to_xml(form) +hl_md(html2ft(h), 'python') +``` + +``` python +Form( + Fieldset( + Input(value='Profit', id='title', name='title', cls='char'), + Label( + Input(type='checkbox', name='done', data_foo='bar', checked='1', cls='checkboxer'), + 'Done', + cls='px-2' + ), + Input(type='hidden', id='id', name='id', value='2'), + Select( + Option(value='a'), + Option(value='b', selected='1'), + name='opt' + ), + Textarea('Details', id='details', name='details'), + Button('Save'), + name='stuff' + ) +) +``` + +``` python +hl_md(html2ft(h, attr1st=True), 'python') +``` + +``` python +Form( + Fieldset(name='stuff')( + Input(value='Profit', id='title', name='title', cls='char'), + Label(cls='px-2')( + Input(type='checkbox', name='done', data_foo='bar', checked='1', cls='checkboxer'), + 'Done' + ), + Input(type='hidden', id='id', name='id', value='2'), + Select(name='opt')( + Option(value='a'), + Option(value='b', selected='1') + ), + Textarea('Details', id='details', name='details'), + Button('Save') + ) +) +``` + +------------------------------------------------------------------------ + +source + +### sse_message + +> sse_message (elm, event='message') + +*Convert element `elm` into a format suitable for SSE streaming* + +``` python +print(sse_message(Div(P('hi'), P('there')))) +``` + + event: message + data:hi
+ data:there
+ data:+ | Type | +Default | +Details | +
---|---|---|---|
appname | +NoneType | +None | +Name of the module | +
app | +str | +app | +App instance to be served | +
host | +str | +0.0.0.0 | +If host is 0.0.0.0 will convert to localhost | +
port | +NoneType | +None | +If port is None it will default to 5001 or the PORT environment +variable | +
reload | +bool | +True | +Default is to reload the app upon code changes | +
reload_includes | +list[str] | str | None | +None | +Additional files to watch for changes | +
reload_excludes | +list[str] | str | None | +None | +Files to ignore for changes | +
there
' in txt +``` + +``` python +@rt('/oops') +def get(nope): return nope +test_warns(lambda: cli.get('/oops?nope=1')) +``` + +``` python +def test_r(cli, path, exp, meth='get', hx=False, **kwargs): + if hx: kwargs['headers'] = {'hx-request':"1"} + test_eq(getattr(cli, meth)(path, **kwargs).text, exp) + +ModelName = str_enum('ModelName', "alexnet", "resnet", "lenet") +fake_db = [{"name": "Foo"}, {"name": "Bar"}] +``` + +``` python +@rt('/html/{idx}') +async def get(idx:int): return Body(H4(f'Next is {idx+1}.')) +``` + +``` python +@rt("/models/{nm}") +def get(nm:ModelName): return nm + +@rt("/files/{path}") +async def get(path: Path): return path.with_suffix('.txt') + +@rt("/items/") +def get(idx:int|None = 0): return fake_db[idx] + +@rt("/idxl/") +def get(idx:list[int]): return str(idx) +``` + +``` python +r = cli.get('/html/1', headers={'hx-request':"1"}) +assert '15, Lorem
" in response +``` + +``` python +# Testing POST with Content-Type: application/json +@app.post("/bodytext") +def index(body): return body + +response = cli.post('/bodytext', headers={"Content-Type": "application/json"}, data=s).text +test_eq(response, '{"b": "Lorem", "a": 15}') +``` + +``` python +files = [ ('files', ('file1.txt', b'content1')), + ('files', ('file2.txt', b'content2')) ] +``` + +``` python +@rt("/uploads") +async def post(files:list[UploadFile]): + return ','.join([(await file.read()).decode() for file in files]) + +res = cli.post('/uploads', files=files) +print(res.status_code) +print(res.text) +``` + + 200 + content1,content2 + +``` python +@rt("/setsess") +def get(sess, foo:str=''): + now = datetime.now() + sess['auth'] = str(now) + return f'Set to {now}' + +@rt("/getsess") +def get(sess): return f'Session time: {sess["auth"]}' + +print(cli.get('/setsess').text) +time.sleep(0.01) + +cli.get('/getsess').text +``` + + Set to 2024-10-11 09:46:57.944986 + + 'Session time: 2024-10-11 09:46:57.944986' + +``` python +@rt("/sess-first") +def post(sess, name: str): + sess["name"] = name + return str(sess) + +cli.post('/sess-first', data={'name': 2}) + +@rt("/getsess-all") +def get(sess): return sess['name'] + +test_eq(cli.get('/getsess-all').text, '2') +``` + +``` python +@rt("/upload") +async def post(uf:UploadFile): return (await uf.read()).decode() + +with open('../../CHANGELOG.md', 'rb') as f: + print(cli.post('/upload', files={'uf':f}, data={'msg':'Hello'}).text[:15]) +``` + + # Release notes + +``` python +@rt("/form-submit/{list_id}") +def options(list_id: str): + headers = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'POST', + 'Access-Control-Allow-Headers': '*', + } + return Response(status_code=200, headers=headers) +``` + +``` python +h = cli.options('/form-submit/2').headers +test_eq(h['Access-Control-Allow-Methods'], 'POST') +``` + +``` python +from fasthtml.authmw import user_pwd_auth +``` + +``` python +def _not_found(req, exc): return Div('nope') + +app,cli,rt = get_cli(FastHTML(exception_handlers={404:_not_found})) + +txt = cli.get('/').text +assert '+ | Type | +Default | +Details | +
---|---|---|---|
db_file | +Optional | +None | +Database file name, if needed | +
render | +Optional | +None | +Function used to render default database class | +
hdrs | +Optional | +None | +Additional FT elements to add to | +
ftrs | +Optional | +None | +Additional FT elements to add to end of | +
tbls | +Optional | +None | +Experimental mapping from DB table names to dict table +definitions | +
before | +Union | +None | +Functions to call prior to calling handler | +
middleware | +Optional | +None | +Standard Starlette middleware | +
live | +bool | +False | +Enable live reloading | +
debug | +bool | +False | +Passed to Starlette, indicating if debug tracebacks should be +returned on errors | +
routes | +Optional | +None | +Passed to Starlette | +
exception_handlers | +Optional | +None | +Passed to Starlette | +
on_startup | +Optional | +None | +Passed to Starlette | +
on_shutdown | +Optional | +None | +Passed to Starlette | +
lifespan | +Optional | +None | +Passed to Starlette | +
default_hdrs | +bool | +True | +Include default FastHTML headers such as HTMX script? | +
pico | +Optional | +None | +Include PicoCSS header? | +
surreal | +Optional | +True | +Include surreal.js/scope headers? | +
htmx | +Optional | +True | +Include HTMX header? | +
ws_hdr | +bool | +False | +Include HTMX websocket extension header? | +
secret_key | +Optional | +None | +Signing key for sessions | +
key_fname | +str | +.sesskey | +Session cookie signing key file name | +
session_cookie | +str | +session_ | +Session cookie name | +
max_age | +int | +31536000 | +Session cookie expiry time | +
sess_path | +str | +/ | +Session cookie path | +
same_site | +str | +lax | +Session cookie same site policy | +
sess_https_only | +bool | +False | +Session cookie HTTPS only? | +
sess_domain | +Optional | +None | +Session cookie domain | +
htmlkw | +Optional | +None | +Attrs to add to the HTML tag | +
bodykw | +Optional | +None | +Attrs to add to the Body tag | +
reload_attempts | +Optional | +1 | +Number of reload attempts when live reloading | +
reload_interval | +Optional | +1000 | +Time between reload attempts in ms | +
static_path | +str | +. | +Where the static file route points to, defaults to root dir | +
kwargs | ++ | + | + |
Returns | +Any | ++ | + |
+ | Type | +Details | +
---|---|---|
css | +str | +CSS to be included in the light media query | +
+ | Type | +Details | +
---|---|---|
css | +str | +CSS to be included in the dark media query | +
+ | Type | +Default | +Details | +
---|---|---|---|
sel | +str | +.marked | +CSS selector for markdown elements | +
+ | Type | +Default | +Details | +
---|---|---|---|
sel | +str | +.marked | +CSS selector for markdown elements | +
inline_delim | +str | +$ | +Delimiter for inline math | +
display_delim | +str | +$$ | +Delimiter for long math | +
math_envs | +NoneType | +None | +List of environments to render as display math | +
+ | Type | +Default | +Details | +
---|---|---|---|
sel | +str | +pre code | +CSS selector for code elements. Default is industry standard, be +careful before adjusting it | +
langs | +str | list | tuple | +python | +Language(s) to highlight | +
light | +str | +atom-one-light | +Light theme | +
dark | +str | +atom-one-dark | +Dark theme | +
+ | Type | +Default | +Details | +
---|---|---|---|
sel | +str | +.sortable | +CSS selector for sortable elements | +
ghost_class | +str | +blue-background-class | +When an element is being dragged, this is the class used to +distinguish it from the rest | +
head
+Let's do this
+`,
+``, ` SHdU {r#6X|+azO!?TL!WU={EgN8fH5)dT@&>!k#Mmc4*4f9~n{qFU
zpq%QsN3b&EMzKI+b-SG&x4c_#!Xh;PzrVWr`gu+Pnz78H37J2)@12Zb6>I&fcpxVQ
zv0$&+%W9MMYQ1iP(c#$0F
+
+```
+
+## Attributes
+
+This example demonstrates many important things to know about how ft
+components handle attributes.
+
+``` python
+#| echo: False
+Label(
+ "Choose an option",
+ Select(
+ Option("one", value="1", selected=True),
+ Option("two", value="2", selected=False),
+ Option("three", value=3),
+ cls="selector",
+ _id="counter",
+ **{'@click':"alert('Clicked');"},
+ ),
+ _for="counter",
+)
+```
+
+Line 2
+Line 2 demonstrates that FastHTML appreciates `Label`s surrounding their
+fields.
+
+Line 5
+On line 5, we can see that attributes set to the `boolean` value of
+`True` are rendered with just the name of the attribute.
+
+Line 6
+On line 6, we demonstrate that attributes set to the `boolean` value of
+`False` do not appear in the rendered output.
+
+Line 7
+Line 7 is an example of how integers and other non-string values in the
+rendered output are converted to strings.
+
+Line 8
+Line 8 is where we set the HTML class using the `cls` argument. We use
+`cls` here as `class` is a reserved word in Python. During the rendering
+process this will be converted to the word “class”.
+
+Line 9
+Line 9 demonstrates that any named argument passed into an ft component
+will have the leading underscore stripped away before rendering. Useful
+for handling reserved words in Python.
+
+Line 10
+On line 10 we have an attribute name that cannot be represented as a
+python variable. In cases like these, we can use an unpacked `dict` to
+represent these values.
+
+Line 12
+The use of `_for` on line 12 is another demonstration of an argument
+having the leading underscore stripped during render. We can also use
+`fr` as that will be expanded to `for`.
+
+This renders the following HTML snippet:
+
+``` python
+Label(
+ "Choose an option",
+ Select(
+ Option("one", value="1", selected=True),
+ Option("two", value="2", selected=False),
+ Option("three", value=3), # <4>,
+ cls="selector",
+ _id="counter",
+ **{'@click':"alert('Clicked');"},
+ ),
+ _for="counter",
+)
+```
+
+``` xml
+
+```
+
+## Defining new ft components
+
+It is possible and sometimes useful to create your own ft components
+that generate non-standard tags that are not in the FastHTML library.
+FastHTML supports created and defining those new tags flexibly.
+
+For more information, see the [Defining new ft
+components](../ref/defining_xt_component) reference page.
+
+## FT components and type hints
+
+If you use type hints, we strongly suggest that FT components be treated
+as the `Any` type.
+
+The reason is that FastHTML leverages python’s dynamic features to a
+great degree. Especially when it comes to `FT` components, which can
+evaluate out to be `FT|str|None|tuple` as well as anything that supports
+the `__ft__`, `__html__`, and `__str__` method. That’s enough of the
+Python stack that assigning anything but `Any` to be the FT type will
+prove an exercise in frustation.
diff --git a/explains/faq.html b/explains/faq.html
new file mode 100644
index 00000000..200e9e80
--- /dev/null
+++ b/explains/faq.html
@@ -0,0 +1,906 @@
+
+
+
+
+
+
+
+
+
+
+Lm
z`0sw9-#$;?Y;8lM#3SVLbHhON2VTG8DQLXQ=B&oI_5{|4&r|sXXym+QVxqid^gHGY
z`3Z9`u5KwB9KQcbUwcwzAbF_Ptsg8e2n}vJHu3Vxu+t74#^!cQAm&r5R02a}J7G#u
zP;v$i?%xvww8?3cb%ESo6X(2cn;-WAR)STZPfvocFDF;#0a;By7=VlnEQXDZjgXDq
zx39Q!GbGB
W%{fOi)&Wj%)Qqi)XPTLzU5X`bZ+2M|F7=?c9b|dY%bx`6
zkxFKo(1^gh78oWOYz1-!T=O;a(ia#EYSXO};B>IEN0TT^p+NIuxx@^I
rYlXX+!2Ce4jcyJa80lw_w$Y!99x?4Dqo$XRIR6(
zap7W=LwqJ9EDX`jXYKulOsKo6I2GpbWZC+@
Z
zxKy1=ctBPnF5K+UC@bAJ1$|5(A-$8+EypM8MWE^BtwZGvhK6;6san$KS-VMQx{#bD
zXu`>-V$_n8JM!jE;AGf($G7mNPUWzYzI*}O#Bn6OSWSgL(z<%L2c0=6%B}gR_LzA~
zp!v$BDAe%>fby>jAu6rG#rmdXI
l(cS`XEJXiwVoL$0v}cnH_Lg
zA0M%ko4DKW8cO7i6651xWTrm*`7r&@_5bs{&~~2ih9uZ#jXUv
zL26(FO?op02Sl%%A((*537G(j1J#Rps^
Socials
+ +OG and Twitter social card headers
++
source
+