Custom CSS
Goal
The theme is close but not quite, and styling components one at a time is getting repetitive. You want to write a rule once and have it apply everywhere.
Before you start
You have used styling helpers and know the box model. CSS adds one idea to what you already do: a selector says which elements a rule applies to, so you no longer have to style one component at a time.
The smallest version
add_website_css takes a selector and a rule body; classes tags
components so selectors can find them:
from drafter import *
add_website_css(".notice", """
border: 2px solid darkslateblue;
border-radius: 8px;
padding: 12px;
margin: 8px 0;
background-color: ghostwhite;
""")
@route
def index() -> Page:
return Page([
Header("Announcements"),
Div("The bake sale moved to Saturday.", classes="notice"),
Div("Captain the cat has opinions about this.", classes="notice"),
"Plain text stays plain."
])
start_server()
Change the rule once, and every notice changes with it. That is
the trade CSS offers: a little setup in exchange for consistency.
Selectors 101
You need only four kinds of selector, listed here in order of usefulness:
| Selector | Applies to | Example |
|---|---|---|
.name |
Components tagged classes="name" |
.notice |
tag |
Every element of that HTML kind | h1, button, a |
.a .b |
Things tagged b inside things tagged a |
.card .title |
#name |
The one component with id="name" |
#scoreboard |
To find which tag a component renders as, check the "External
links" section of its reference page: a Header(...) at level 1 is
an h1, and every Button is a button.
from drafter import *
add_website_css("h1", "color: darkslateblue; letter-spacing: 2px;")
add_website_css("button", "border-radius: 999px; padding: 8px 16px;")
@route
def index() -> Page:
return Page([
Header("Rounded World"),
Button("Every button", "index"),
Button("Gets the treatment", "index")
])
start_server()
Both forms of add_website_css
add_website_css(".notice", "border: 2px solid navy;")
add_website_css(".notice { border: 2px solid navy; }")
With two arguments, the first is the selector and the second is the declarations that belong inside the braces. With one argument, you are handing over raw CSS, which can contain several rules at once.
Style tags inside a page
CSS from add_website_css applies to the whole site. When only one
page needs a rule, the Page's css parameter attaches the rule
to that page alone. This is an advanced feature documented on
Page.
Where this sits with themes
Themes are CSS too, and they load before yours, so your rules
usually win when they target the same element precisely. A strongly
opinionated theme can still override you; when your rule seems to
be ignored, see the specificity gotcha. To build a
look entirely your own, start from set_website_style("none"), the
blank slate.
Common problems
- The rule does nothing: check the selector against what is
actually rendered.
classes="notice"pairs with.notice(the dot belongs in the selector, not in the keyword), and a misspelled selector fails silently. - The rule styles too much:
buttonstyles every button, including ones you forgot about. Prefer classes for anything less than a sitewide decision. - The theme overrides your rule: see Styling gotchas for specificity and the ways around it.
- It styles the debug panel too: broad selectors like
bodyalso reach Drafter's own interface. Target your content instead; the gotchas page names the right container.
Understand it
Design basics explains the reasoning behind the rules. Selectors are the only new machinery on this page.
See another example
The styling playground has the three tiers side by side, cards included.
Look it up
add_website_css and classes.
Fix a problem
Styling gotchas collects the surprising parts of styling in one place.