Skip to content

Styling in Folio 2.0

Folio 2.0 applies styles directly to elements in the template or to PHP node builders. The style system is intentionally small and explicit: every value is resolved before layout, so the output is predictable.

Style attributes

Most elements accept these attributes:

AttributeTypeEffect
widthlength or %Element width. 100% fills the parent container.
heightlength or %Element height.
paddingnumberInner spacing on all sides (points).
gapnumberSpace between children in a column or row.
backgroundcolor hexSolid background fill.
colorcolor hexText color.
fontSizenumberText size in points.
fontWeightbold / normalCurrently maps to Helvetica Bold or normal.
alignleft / right / center / justifyText alignment.
grownumberFlex grow factor for column/row children.
colspanintegerTable cell column span.

Lengths are in points (1/72 inch) unless you append a %, which makes them relative to the available width.

Design tokens and themes

Load a theme with @theme "name". A theme is a JSON file that groups values into categories such as colors, fontSizes, space and radii.

json
{
  "tokens": {
    "colors": {
      "brand": "#1e3a8a",
      "surface": "#f8fafc"
    },
    "fontSizes": {
      "2xl": 20
    },
    "space": {
      "4": 12
    }
  },
  "styles": {
    "brand": {
      "color": "{colors.brand}",
      "fontSize": "{fontSizes.2xl}"
    }
  }
}

Reference tokens in attributes or style blocks with {category.name}:

folio
@theme "modern"

page {
    text(class="brand") "Branded heading"
}

@style blocks

Add CSS-like rules that apply by class or element type. These are scoped to the document that contains them.

folio
@style {
    .hero {
        background: {colors.surface};
        padding: 24;
        color: {colors.brand};
    }

    heading {
        fontWeight: bold;
    }
}

page {
    column {
        heading(class="hero") "Title"
    }
}

Colors

Folio 2.0 accepts 6-digit hex colors:

folio
text(color="#0f172a") "Dark slate"
text(color="#ef4444") "Red alert"

A short palette used across the examples:

  • #0f172a — dark navy (headers)
  • #334155 — slate (sub-headings)
  • #64748b — muted slate (secondary text)
  • #94a3b8 — light slate (subtle labels)
  • #f8fafc — near white (card backgrounds)
  • #ffffff — white

Flex layout

column and row are the two flex containers.

  • column stacks children vertically.
  • row places children horizontally.
  • gap sets the spacing between children.
  • align on the container controls cross-axis alignment.
  • grow on a child makes it expand to fill leftover space.
folio
row(gap=24) {
    column(grow=1) { text "Left side" }
    column(grow=1) { text "Right side" }
}

Text styling

Text inherits the current color and font size from its parent. You can also set it explicitly:

folio
column(padding=24, gap=8) {
    heading(fontSize=20, color="#0f172a") "Title"
    text(fontSize=11, color="#64748b") "Body copy in a muted color."
    text(fontSize=11, fontWeight="bold") "Emphasised inline text."
}

Table styling

Tables accept padding, background and width. Rows accept background, fontSize and fontWeight. Cells accept align, background and colspan.

folio
table(padding=12, background="#f8fafc", width="100%") {
    header(background="#0f172a", color="#ffffff", fontSize=10, fontWeight="bold") {
        th "Description"
        th(align="right") "Qty"
    }
    tr(background="#ffffff", fontSize=10) {
        td "Premium service"
        td(align="right") "2"
    }
}

Style inheritance means fontWeight="bold" on the header row makes each th inside bold.

Complete theme example

A real theme keeps every color, spacing and font-size decision in one JSON file:

json
{
  "name": "pro",
  "tokens": {
    "colors": {
      "brand": "#1e3a8a",
      "surface": "#f8fafc",
      "muted": "#64748b",
      "ink": "#0f172a",
      "paper": "#ffffff"
    },
    "fontSizes": {
      "2xl": 26,
      "xl": 20,
      "lg": 14,
      "base": 11,
      "sm": 9
    },
    "space": {
      "4": 12,
      "8": 24,
      "12": 48
    },
    "radii": {
      "md": 4,
      "lg": 8
    }
  },
  "styles": {
    "brand": {
      "color": "{colors.brand}",
      "fontSize": "{fontSizes.xl}",
      "fontWeight": "bold"
    },
    "muted": {
      "color": "{colors.muted}",
      "fontSize": "{fontSizes.sm}"
    }
  }
}

Use it in a template:

folio
@theme "pro"

@style {
  .card {
    background: {colors.surface};
    padding: {space.8};
    radius: {radii.lg};
  }

  .total {
    color: {colors.ink};
    fontSize: {fontSizes.xl};
    fontWeight: bold;
  }
}

page(background="{colors.paper}") {
  column(width="100%", padding="{space.12}", gap="{space.8}") {
    heading(class="brand") "Invoice"
    column(class="card") { ... }
    text(class="total") total
  }
}

Style cascade

Folio resolves styles in this order, with later steps overriding earlier ones:

  1. Inherited values from the parent element.
  2. Theme named styles for each class on the element.
  3. Utility classes such as p-4, rounded-md and text-brand.
  4. Styles from the document's @style block that match the element type or class.
  5. Inline attributes on the element (color="#0f172a", fontSize=12).

This means you can set a default with a class or @style rule and override it with an inline attribute when necessary.

What is not yet supported

PandaCSS-style recipes and slot recipes, gradients, and filters are not yet implemented.

Engineered for teams that care about predictable PDF output.