Style

The closed list of visual keys a document may set, what each one accepts, and the five kinds of value you can write.


A style block sits on any element, and on the document itself. It looks like a React inline-style object, because that is exactly what it is modelled on:

"style": { "fontSize": 14, "fontWeight": "semibold", "color": "var(--color-muted-foreground)" }

The key list is closed. Everything the language can say is on this page, and a key that is not on it comes back as an error rather than being quietly ignored — you would otherwise never learn that boxShadowColor does nothing.

The five kinds of value

Before the tables, the value shapes. There are only five, and they are the reason a document can be checked before it is saved.

A number is pixels. "fontSize": 14, "gap": 8. Whole numbers, and each key has its own sensible range — the Accepts column below gives it. A number outside the range is rejected, not clamped silently.

A preset is a named step. "fontSize": "lg", "borderRadius": "full", "gap": "md". Presets resolve through your theme's scale, so a preset follows a theme change and a hard-coded pixel value does not. A preset and a pixel value are equally safe; pick whichever says what you mean.

Spacing can be per side. padding and margin take a single number, a preset, or an object:

"padding": { "x": 14, "top": 2, "bottom": 12 }

x is left and right, y is top and bottom, and a named side wins over the shorthand it overlaps. Any side you do not mention is 0. What you cannot write is the CSS shorthand string "2px 14px" — a phone cannot parse it, so it is not part of a language both screens speak.

Colours and fonts are tokens, not paint. The form to reach for is your theme's own variable — "color": "var(--color-foreground)", "background": "var(--color-primary)", "fontFamily": "var(--font-heading)". Those follow whatever theme is active, including one you switch to next month. A strict hex ("#16a34a") and a colour name from your theme's palette are accepted too, and stay exactly that colour forever.

Everything else is a closed token or a boolean. "textAlign": "center", "objectFit": "cover", "boxShadow": "md", "stretch": true. Each one's full list is in the Accepts column.

One document, two screens

Every key below is honoured by both renderers — the browser and the phone app — and that is checked automatically rather than promised. A few land differently under the hood: lineHeight is a ratio the phone multiplies out into pixels, span and columns become real CSS Grid on the web and arithmetic on the phone, maxLines becomes a line clamp on one and a line limit on the other. You write the same thing either way.

Typography

How words are set: size, weight, family, colour, alignment.

KeyValueAcceptsWhat it doesExample
fontSizepixels or a preset tokeninteger 8–96 or xs | sm | base | lg | xl | 2xl | mdText size in pixels, or a step on the theme's type scale"fontSize": 14
fontWeightwhole number or a preset tokeninteger 100–900 or normal | medium | semibold | boldStroke weight on the CSS 100–900 ladder, or a preset word"fontWeight": "semibold"
fontFamilyfontvar(--font-<slot>) or a bundled font idA theme font slot (`var(--font-heading)`) or a bundled font id"fontFamily": "var(--font-heading)"
colorcolourvar(--color-<role>), role:<name>, a palette name, or #rrggbbText colour — a theme variable, a role, a palette name, or a hex"color": "var(--color-foreground)"
textAlignclosed tokenleft | center | rightHorizontal alignment of the text inside its box"textAlign": "center"
lineHeightnumbernumber 0.8–3Line spacing as a MULTIPLE of the font size"lineHeight": 1.4
letterSpacingnumbernumber -2–8Extra space between glyphs, in pixels"letterSpacing": 0.5
textTransformclosed tokennone | uppercase | lowercase | capitalizeRecase the text without changing the stored value"textTransform": "uppercase"
fontStyleclosed tokennormal | italicUpright or italic"fontStyle": "italic"
textDecorationLineclosed tokennone | underline | line-throughUnderline or strike the text"textDecorationLine": "underline"
maxLineswhole numberinteger 1–24Clamp the text to at most this many lines"maxLines": 2

Box

The element's own box — fill, border, spacing, size, and how a picture fills it.

KeyValueAcceptsWhat it doesExample
backgroundcolourvar(--color-<role>), role:<name>, a palette name, or #rrggbbFill behind the element"background": "var(--color-primary)"
borderRadiuspixels or a preset tokeninteger 0–48 or none | sm | md | lg | xl | fullCorner rounding in pixels, or a preset step (`full` is a pill)"borderRadius": 12
borderWidthpixels or a preset tokeninteger 0–12 or none | thin | medium | thickBorder thickness in pixels, or a preset step"borderWidth": 1
borderColorcolourvar(--color-<role>), role:<name>, a palette name, or #rrggbbBorder colour"borderColor": "var(--color-border)"
paddingpixels, or a per-side objectinteger 0–96, none | xs | sm | md | lg, or { top, right, bottom, left, x, y }Inner spacing — one number, a preset, or per side with `x` / `y` shorthands"padding": {"x":14,"top":2,"bottom":12}
marginpixels, or a per-side objectinteger 0–96, none | xs | sm | md | lg, or { top, right, bottom, left, x, y }Outer spacing — one number, a preset, or per side with `x` / `y` shorthands"margin": {"y":8}
widthpixelsinteger 0–2000Fixed width in pixels"width": 120
heightpixelsinteger 0–2000Fixed height in pixels"height": 44
minWidthpixelsinteger 0–2000Smallest width the element may shrink to, in pixels"minWidth": 44
minHeightpixelsinteger 0–2000Smallest height the element may shrink to, in pixels"minHeight": 44
maxWidthpixelsinteger 0–2000Largest width the element may grow to, in pixels"maxWidth": 320
maxHeightpixelsinteger 0–2000Largest height the element may grow to, in pixels"maxHeight": 320
aspectRationumber or a closed tokennumber 0.1–10, or square | video | portrait | autoWidth ÷ height — a ratio, or a named box (`square`, `video`, `portrait`, `auto`)"aspectRatio": "square"
objectFitclosed tokencover | containHow a picture fills its box"objectFit": "cover"

Layout

How an element arranges its children, and how it sits among its siblings.

KeyValueAcceptsWhat it doesExample
gappixels or a preset tokeninteger 0–96 or none | xs | sm | md | lgSpace between children, in pixels or a preset step"gap": 8
flexDirectionclosed tokenrow | columnLay the children out across or down"flexDirection": "row"
alignItemsclosed tokenstart | center | end | stretchWhere the children sit on the cross axis"alignItems": "center"
justifyContentclosed tokenstart | center | end | between | around | evenlyHow the children are distributed along the main axis"justifyContent": "between"
flexWrapclosed tokenwrap | nowrapWhether a row may break onto another line"flexWrap": "wrap"
spanwhole numberinteger 1–12How many columns of the parent grid this element covers"span": 2
rowSpanwhole numberinteger 1–12How many rows of the parent grid this element covers"rowSpan": 2
orderwhole numberinteger 0–999Where this element sits among its siblings, lowest first"order": 2
columnswhole numberinteger 1–8Lay the children out in this many equal columns"columns": 3
stretchbooleantrue or falseFill the space available (with `columns`, stretch the tracks)"stretch": true

Effects

Depth and transparency, from the one app-wide table.

KeyValueAcceptsWhat it doesExample
opacitynumbernumber 0–1How opaque the element is, 0 to 1"opacity": 0.6
boxShadowclosed tokennone | sm | md | lgDepth under the element, from the one app-wide shadow table"boxShadow": "md"

Next