by Audrey M. Roy Greenfeld | Mon, Jan 20, 2025
How to make a website check the user's preferred mode and set the background appropriately.
from fasthtml.common import * from fasthtml.jupyter import * from fastcore.utils import *
Open your website in 2 Chrome profiles, one with light and one with dark mode. For example, in mine I noticed:
This told me that I need to modify the page background styles depending on the preferred mode.
In Chrome, go to Option
Cmd
c
to open "Inspect Elements" > Elements > Styles. Click the upside-down paintbrush icon to bring up a menu of:
prefers-color-scheme: light
prefers-color-scheme: dark
main.py
With color-scheme
To enable support for the CSS light-dark() color function, you set this on :root
:
Style(':root {color-scheme: light dark;}')
This allows your site to respect the user's light or dark mode preference.
Now wherever you specify CSS colors, you can specify a pair of colors.
Style('body {background-color: light-dark(#ffffff, #1a1a1a); color: light-dark(#1a1a1a, #ffffff);}')
Here in this example:
#ffffff
is <body>
's background color in light mode#1a1a1a
is <body>
's background color in dark modeIf you use these values a lot, define CSS variables --light-*
and --dark-*
If you find yourself repeating color constants a lot, define CSS variables in :root
and use them in light-dark()
like:
Style(""":root {color-scheme: light dark; --lightshade: #ffffff; --darkshade: #1a1a1a;} body {background-color: light-dark(var(--lightshade), var(--darkshade))};""")
Here I create a component with light-dark colors. The component respects light/dark mode preference. I need stuff to put into cards, so I'm grabbing my notebooks:
nbs = L(Path('../arg-blog-fasthtml/nbs').glob('*.ipynb')) nbs
def LightDarkNotebookCard(nb): "A card showing notebook info" return Div(H3(nb.name), P("Lorem ipsum dolor sit amet."), style="border: 3px solid light-dark(#eaeaea, #111111); padding: 10px; margin: 2px; border-radius: 4px; background-color: light-dark(#eeeeee, #1a1a1a); color: light-dark(#1a1a1a, #eeeeee);") show(LightDarkNotebookCard(nbs[0]))
It's nice to create a rough component, then use AI to iterate on the design. Here I pasted my code into Claude with the prompt:
Make these FastHTML cards look better. No React or Tailwind, please.
That gave me this improved card FT component:
def LightDarkNotebookCard(nb): "A card showing notebook info with enhanced styling" return Div( H3(nb.name, style="margin: 0 0 12px 0; font-size: 1.3em;"), P("Lorem ipsum dolor sit amet.", style="margin: 0; line-height: 1.5; opacity: 0.9;"), style=""" border: 1px solid light-dark(#e0e0e0, #333333); padding: 20px; margin: 8px; border-radius: 8px; background-color: light-dark(#ffffff, #222222); color: light-dark(#1a1a1a, #ffffff); box-shadow: 0 2px 4px light-dark(rgba(0,0,0,0.05), rgba(0,0,0,0.2)); transition: all 0.2s ease; cursor: pointer; """, onmouseover=""" this.style.transform = 'translateY(-2px)'; this.style.boxShadow = '0 4px 8px light-dark(rgba(0,0,0,0.1), rgba(0,0,0,0.3))'; """, onmouseout=""" this.style.transform = 'translateY(0)'; this.style.boxShadow = '0 2px 4px light-dark(rgba(0,0,0,0.05), rgba(0,0,0,0.2))'; """ ) show(LightDarkNotebookCard(nbs[0]))
Here I map them to a handful of list items, to make them look more realistic:
def LightDarkList(nbs): "FT component that returns a list of notebooks" return Div(*nbs.map(LightDarkNotebookCard), style="columns: 3;") show(to_xml(LightDarkList(nbs[:9])))
You can see this component in action by creating a little FastHTML app with this :root
style:
app, rt = fast_app(hdrs=(picolink, Style(':root {color-scheme: light dark;}')))
@rt def index(): return Main(H1("My Site"), LightDarkList(nbs[:9]))
I run this app from my notebook via the next line. If using a main.py
, replace it with serve()
.
# server = JupyUvi(app)
# server.stop()
color-scheme: light dark
in :root
to respect the user's dark/light mode preference.light-dark()
function to set properties based on mode