.szs — CSS with logic
serez-ui styles its GUI with .szs: a small CSS dialect with the same selectors and properties you already know, but where conditions are reactive — re-evaluated every frame against your component state and the live window size. It is parsed in pure Serez-Code (no CSS engine), so it stays tiny and predictable.
.szs owns the design (everything visual — color, spacing, alignment, sizing, visibility), while your .szx (JSX) owns the logic and structure — like CSS vs HTML/JS. .szs is a serez-ui feature (parsed by parseCss), not a core built-in; it styles the GUI renderer, and the editor extension highlights .szs files.Attach a stylesheet
Load the sheet from a file and hand it to your Window before runGui. Stylesheets live in .szs files (not inline strings): Serez-Code interpolates {} inside string literals, so reading from a file keeps the CSS braces literal. Reading a file needs the File permission.
app.useStylesheet(parseCss(File.read("app.szs"))) // before runGui
app.runGui("App", 640, 480)Selectors & properties
A rule is selector { property: value; }. The selector is a tag name (body, h1, Button, Row, …) or * (every tag). When several rules set the same property, the last matching one wins.
| Property | Applies to | Values | Effect |
|---|---|---|---|
background-color | body, Button, Input, Select, Checkbox, Dropdown, Textarea | hex / name | fill color |
color | text tags + controls | hex / name | text color |
direction | Row | column | lay a Row out vertically |
font-scale | h1 / h2 / h3 / p / span / li | integer | text-size multiplier |
white-space | h1 / h2 / h3 / p / span / li | nowrap | keep on one line (no wrap; may clip) |
text-align | h1 / h2 / h3 / p / span / li / Label | left / center / right | horizontal text alignment |
padding | containers (div, Col, section, form, ul) | integer px | inner spacing |
margin-bottom | any tag | integer px | extra space below the element |
gap | Row | integer px | space between Row children |
display | any tag | none | hide the element (reactive) |
font-family | text tags + controls (body = app default) | family name / :font alias | proportional text in that font |
border-radius | Button, Input, Textarea, Select, Dropdown, ProgressBar | integer px | antialiased rounded corners |
Colors are hex (#rgb or #rrggbb) or a basic name (white black red green blue yellow gray). Sizes are plain integers (pixels) — no px unit.
/* app.szs */
body { background-color: #0f172a; }
h1 { color: #ffd166; font-scale: 3; }
p { color: #94a3b8; }
Button { background-color: #2563eb; color: #ffffff; }property: value, so unknown ones are simply ignored — more get wired to the renderer as serez-ui grows (see Looking ahead).Fonts — :font and font-family
Declare font files in a :font block (the renderer loads them lazily) and pick families per tag with font-family. System-installed fonts work by name without any block; a downloaded .ttf (e.g. a Google Font) goes through :font. Set it on body and the whole app inherits it.
:font {
Titular: fonts/Roboto.ttf; /* alias: path to a .ttf/.otf */
}
body { font-family: "Segoe UI"; } /* app-wide default (system font) */
h1 { font-family: "Titular"; } /* the :font alias */Custom families render proportionally (real glyph advances, antialiased) and text wrapping measures with the actual font. Input / Textarea content stays monospace so caret positioning holds.
Reactive conditions — the logic
Any selector can carry one condition in parentheses, re-checked every frame: selector (var OP literal) { … }. Operators: == != < > <= >=. If the variable is not available, the rule simply does not apply.
body (count == 0) { background-color: #0f172a; }
body (count != 0) { background-color: #14532d; }The variables come from two places: your component state (via styleVars()) and the built-in width / height (the live window size, in px).
Exposing state — styleVars()
Override styleVars() to publish the state your conditions read. (An optional :import { … } block at the top of the sheet documents which variables you use, but the values are taken from styleVars() / width / height directly.)
public any styleVars() { return [["count", this.count]] }Responsive — media queries
width and height (px) are always available — no styleVars() needed — so .szs doubles as a responsive system, and it reflows live as the window resizes:
body (width < 600) { background-color: #1e1b4b; } /* phone-ish */
body (width >= 600) { background-color: #0f172a; }
Row (width < 600) { direction: column; } /* stack the row when narrow */
h1 (width < 600) { font-scale: 2; }
h1 (width >= 960) { font-scale: 4; }Pair this with code-level breakpoints (app.breakpoint()) and automatic Row wrapping — see the serez-ui responsive section.
A complete example
/* counter.szs */
body (count == 0) { background-color: #0f172a; }
body (count != 0) { background-color: #14532d; }
h1 (width < 600) { font-scale: 2; }
h1 (width >= 600) { font-scale: 3; }
h1 { color: #ffd166; }
Button { background-color: #2563eb; color: #ffffff; }// counter.sz
class Counter:Window {
public Counter() { super(); this.count = 0 }
public any render() {
return h("div", [], [
h("h1", [], ["Count: " + this.count]),
h("Button", [["onClick", () => { this.count = this.count + 1 }]], ["+1"])
])
}
// expose state to the .szs conditions
public any styleVars() { return [["count", this.count]] }
}
let app = new Counter()
app.useStylesheet(parseCss(File.read("counter.szs")))
app.runGui("Counter", 520, 360)Notes & limits
- One condition per selector — no
and/or; write several rules instead. - Properties are limited to the table above; other declarations parse but are ignored.
- Last matching rule wins for a given property.
- Read the sheet from a
.szsfile, not an inline string. - Colors: hex (
#rgb/#rrggbb) or a basic name.
Looking ahead
.szs is intentionally small but built to grow: the parser already accepts arbitrary property: value declarations, so new properties (spacing, borders, alignment) can be wired to the renderer without a new syntax, and conditions/selectors can gain power over time. The surface documented above is what is wired up today — this page tracks it as it expands.