Reference

Style Properties Reference

Every node (except slot) accepts a style object that defines its visual appearance. Styles are written in JSON using camelCase CSS property names and are compiled to scoped CSS classes during the static build.


Style Object Format

The style object uses responsive keys. Only base is required. The tablet and mobile keys override specific properties at their respective breakpoints.

{
  "style": {
    "base": {
      "padding": "24px",
      "backgroundColor": "var(--background)",
      "fontSize": "16px",
      "display": "flex",
      "flexDirection": "column",
      "gap": "12px"
    },
    "tablet": {
      "padding": "16px",
      "fontSize": "14px"
    },
    "mobile": {
      "padding": "12px",
      "fontSize": "13px",
      "flexDirection": "column"
    }
  }
}

Breakpoint names and widths are defined in project.config.json under breakpoints. The default configuration is:

Breakpoint

Max Width

tablet

1024px

mobile

540px

Properties not overridden in tablet or mobile inherit from base.


CSS Property Names

All CSS property names use camelCase. Common properties:

JSON Key

CSS Property

backgroundColor

background-color

fontSize

font-size

fontWeight

font-weight

lineHeight

line-height

letterSpacing

letter-spacing

borderRadius

border-radius

flexDirection

flex-direction

justifyContent

justify-content

alignItems

align-items

gridTemplateColumns

grid-template-columns

textDecoration

text-decoration

boxShadow

box-shadow

zIndex

z-index

aspectRatio

aspect-ratio

Values are always strings (even numeric values like zIndex):

{
  "style": {
    "base": {
      "zIndex": "10",
      "opacity": "0.8",
      "flexGrow": "1"
    }
  }
}

Colors

Always use CSS custom properties from colors.json with the var() function. Raw hex values do not integrate with the editor's color system.

{
  "style": {
    "base": {
      "color": "var(--text-primary)",
      "backgroundColor": "var(--surface)",
      "borderColor": "var(--border)"
    }
  }
}

Responsive Scales

When responsiveScales is enabled in project.config.json, certain property types are automatically scaled down at smaller breakpoints. This means you only need to define base values for many properties.

Scaled property types and their default multipliers:

Property Type

Tablet

Mobile

fontSize

0.8x

0.7x

padding

0.75x

0.5x

margin

0.7x

0.45x

gap

0.65x

0.4x

Explicit tablet or mobile overrides take precedence over auto-scaling.


Style Mappings

Style mappings let prop values control CSS properties. Use _mapping objects in place of static values.

{
  "style": {
    "base": {
      "padding": "16px",
      "backgroundColor": {
        "_mapping": true,
        "prop": "variant",
        "values": {
          "primary": "var(--primary)",
          "secondary": "var(--secondary)",
          "ghost": "transparent"
        }
      },
      "fontSize": {
        "_mapping": true,
        "prop": "size",
        "values": {
          "small": "14px",
          "medium": "16px",
          "large": "20px"
        }
      }
    }
  }
}

Mapping Object Properties

Field

Type

Description

_mapping

true

Required marker

prop

string

Interface prop name to read the value from

values

`Record<string, string \

number>`

Map from prop values to CSS values

The rendered CSS uses the value from values that matches the current prop value. If no match is found, the property is omitted.


Interactive Styles

Interactive styles define CSS rules for pseudo-states (:hover, :focus, :active) and conditional class selectors. They are an array of rule objects on the interactiveStyles property.

{
  "type": "node",
  "tag": "button",
  "style": {
    "base": { "backgroundColor": "var(--primary)", "color": "var(--on-primary)" }
  },
  "interactiveStyles": [
    {
      "name": "onHover",
      "postfix": ":hover",
      "style": { "base": { "opacity": "0.8", "transform": "translateY(-1px)" } }
    },
    {
      "name": "onFocus",
      "postfix": ":focus-visible",
      "style": { "base": { "outline": "2px solid var(--focus-ring)", "outlineOffset": "2px" } }
    },
    {
      "name": "onActive",
      "postfix": ":active",
      "style": { "base": { "transform": "translateY(0)" } }
    }
  ]
}

Interactive Style Rule Properties

Field

Type

Required

Description

name

string

No

Friendly name for the rule (displayed in editor)

prefix

string

No

CSS selector prefix before the element class

postfix

string

No

CSS selector postfix after the element class

style

StyleObject

Yes

Responsive style object applied when selector matches

previewProp

string

No

Boolean prop name that toggles preview in the editor

Selector Pattern

The generated CSS follows the pattern: {prefix}.element-class{postfix} { ... }

Examples of prefix/postfix combinations:

prefix

postfix

Generated Selector

Use Case

(none)

:hover

.el-abc:hover

Hover state

(none)

:focus-visible

.el-abc:focus-visible

Keyboard focus

(none)

.is-active

.el-abc.is-active

Active class toggle

".dark "

:hover

.dark .el-abc:hover

Dark mode hover

".dark "

.is-active

.dark .el-abc.is-active

Dark mode active

Note the trailing space in prefix values like ".dark " -- this creates a descendant selector.


Conditional Rendering (if)

The if property controls whether a node is rendered. It supports three formats.

Static Boolean

{
  "type": "node",
  "tag": "div",
  "if": false,
  "children": "This node will not render."
}

Prop-Based Mapping

{
  "type": "node",
  "tag": "div",
  "if": {
    "_mapping": true,
    "prop": "showBanner",
    "values": { "true": true, "false": false }
  },
  "children": "Conditionally visible based on showBanner prop."
}

Template Expression

{
  "type": "node",
  "tag": "div",
  "if": "{{showPromo}}",
  "children": "Rendered when showPromo is truthy."
}

In list contexts, item fields can be used:

{
  "type": "node",
  "tag": "span",
  "if": "{{item.featured}}",
  "children": "Featured"
}

generateElementClass

Set generateElementClass: true to output a scoped class name on the element without attaching any styles. This is useful when you need a stable class target for custom CSS or JavaScript.

{
  "type": "node",
  "tag": "div",
  "generateElementClass": true,
  "children": "Has a class but no inline styles."
}

Complete Example

A card component structure demonstrating all style features:

{
  "type": "node",
  "tag": "article",
  "style": {
    "base": {
      "padding": "24px",
      "borderRadius": "12px",
      "backgroundColor": "var(--surface)",
      "boxShadow": {
        "_mapping": true,
        "prop": "elevated",
        "values": {
          "true": "0 4px 12px rgba(0,0,0,0.1)",
          "false": "none"
        }
      },
      "border": "1px solid var(--border)"
    },
    "tablet": {
      "padding": "16px",
      "borderRadius": "8px"
    },
    "mobile": {
      "padding": "12px"
    }
  },
  "interactiveStyles": [
    {
      "name": "hover",
      "postfix": ":hover",
      "style": {
        "base": {
          "borderColor": "var(--primary)",
          "transform": "translateY(-2px)",
          "boxShadow": "0 8px 24px rgba(0,0,0,0.12)"
        }
      }
    }
  ],
  "children": [
    {
      "type": "node",
      "tag": "h3",
      "style": { "base": { "fontSize": "20px", "fontWeight": "600", "margin": "0 0 8px 0" } },
      "children": "{{title}}"
    },
    {
      "type": "node",
      "tag": "p",
      "if": {
        "_mapping": true,
        "prop": "showDescription",
        "values": { "true": true, "false": false }
      },
      "style": { "base": { "color": "var(--text-secondary)", "fontSize": "14px" } },
      "children": "{{description}}"
    }
  ]
}

Next Steps

Building the future of digital experiences, one website at a time

© 2026 Company. All rights reserved.