3: CSS

Phlo has its own compact CSS syntax: no semicolons, no braces required for single rules, and $variables that transpile to CSS custom properties. In this chapter you give the poll a dark card layout and a small theme, in a dedicated style file.

3.1: A style file

Style can live in any .phlo file, but a dedicated file keeps things tidy. Create poll.style.phlo:

<style>
body {
	margin: 0
	background: #0d0f12
	font-family: system-ui, sans-serif
}
main.poll {
	max-width: 560px
	margin: 8vh auto
	padding: 0 24px
}
</style>

Every <style> block transpiles into a bundle. Without an ns= attribute it lands in the default app namespace, which becomes www/app.css and is linked by view() automatically. Only a non-default bundle needs the attribute, for example <style ns=docs> for a separate docs.css.

Reload http://localhost/poll: centered column, dark background. The build ran automatically on the request and regenerated app.css.

3.2: One declaration per line

The CSS parser treats every line as one complete declaration, and the colon does double duty as both selector nesting and property separator:

.card {
	background: #16181d
	border-radius: 12px
	padding: 24px
	margin-bottom: 16px
}
h1: margin: 0 0 24px
.card: p: line-height: 1.6em

h1: margin: 0 0 24px is a complete rule on one line. .card: p: ... nests: it transpiles to .card p { line-height: 1.6em; }. Never wrap a value across lines unless you end the property line with a bare : or end each continuation line with a comma; anything else is a build error.

Add the .card block and the h1 line to poll.style.phlo and reload: the buttons now sit in a card.

3.3: Theme variables

$name in a style block becomes the CSS custom property --name, and every place you use it becomes var(--name). Define the theme once in :root and use it everywhere. This is the full poll.style.phlo for the rest of the tutorial:

<style>
:root {
	$text: #f4f4f5
	$surface: #16181d
	$primary: #ff4a00
	$border: #2a2e36
	$muted: #9aa0aa
}
body {
	margin: 0
	background: #0d0f12
	color: $text
	font-family: system-ui, sans-serif
}
main.poll {
	max-width: 560px
	margin: 8vh auto
	padding: 0 24px
}
h1: margin: 0 0 24px
.card {
	background: $surface
	border: 1px solid $border
	border-radius: 12px
	padding: 24px
	margin-bottom: 16px
}
form: display: inline
button {
	background: $primary
	color: $text
	border: none
	border-radius: 8px
	padding: 10px 18px
	margin-right: 8px
	cursor: pointer
}
</style>

Five variables carry the whole theme: $text, $surface, $primary, $border, and $muted (you use $muted in the next chapter for the vote counts). Want a light theme later? Change five lines in :root.

3.4: Check the output

Look at what the transpiler produced:

docker run --rm -v $(pwd)/app:/app ghcr.io/q-ainl/phlo cat /app/www/app.css

You see regular CSS: :root { --text: #f4f4f5; ... }, button { background: var(--primary); ... }. Phlo writes the semicolons, the braces, and the var() wrappers; you write one declaration per line.

Reload http://localhost/poll: a dark card with the question and four orange buttons. The page looks like a poll now. It just cannot count yet.

Last updated on 23 August 2026

We use essential cookies to make this site work. With your permission we also use analytics to improve the site.