Skip to content

Themes

How Ringroad themes are structured and how a storefront page renders.

Updated View as Markdown

A Ringroad theme is a directory of Ringroad Jinja files plus a theme.json manifest. It is server-side rendered (SSR) and sectioned: a merchant composes pages section by section in the theme editor instead of hand-editing templates.

The five parts of a theme

themes/{slug}/
├── theme.json          # manifest: global settings, page presets, section groups
├── layout/             # the page shell (theme.jinja)
├── nodes/              # every node type, sections AND content atoms
├── templates/          # whole-page templates (cart, checkout, 404, account/*)
├── snippets/           # reusable Jinja macros (buttons, inputs, icons, …)
└── assets/             # static main.css / main.js served as /_assets/*

The directory name becomes the theme slug (default for themes/default/). A store picks one active theme; the API reads it from stores.current_theme and builds a rendering engine over the whole themes/ directory at startup.

Nodes are the unit of editing

The core idea of the theme system: everything a merchant edits is a node. There is no longer a hard distinction between a “section” and a “block”, both are nodes with different roles:

Role Marker Editor placement
section @editor:section at the top of the file Top-level: “Add section” panel, page or section group
simple @editor:any Nested inside another node: “Add child” panel

themes/default/nodes/ has 45 node types: layout containers (hero, columns, rows, slideshow), navigation (navbar, navlink, navmenu), commerce listings (catalog_products, featured_products, collection_products), product-detail atoms (product_title, product_price, buy_buttons, variant_picker), and marketing/footer (newsletter, testimonials, footer).

Every node file declares three things in one place:

  1. Markup, the Jinja body that renders the node,
  2. {% schema %}, JSON describing settings, accepted children, slots, and presets for the editor,
  3. {% stylesheet %} / {% javascript %}, CSS and JS scoped to the node (optional).

How a page renders

flowchart LR
  R[Request] --> E[Engine::render_page]
  E --> L[load_config: page tree]
  E --> H[header-group nodes]
  E --> B[page nodes]
  E --> F[footer-group nodes]
  H --> N[render_node × n]
  B --> N
  F --> N
  N --> C[CSS subset + vars]
  N --> S[JS blocks]
  E --> T[layout/theme.jinja]
  T --> OUT[Rendered HTML]
  1. The API route handler resolves the store’s theme and builds a StorefrontContext (shop, product, cart, collections, …) and an extra.* bag of page data.
  2. Engine::render_page() loads the page’s node tree, persisted merchant edits from store_templates.config, falling back to the theme preset, plus the shared header-group and footer-group node trees.
  3. Each node renders recursively (children inside parents), settings merged with schema defaults.
  4. Rendered node types drive CSS subsetting: only the stylesheet blocks of the types actually present on the page (plus their accepted children) are inlined as page_styles; JavaScript blocks are concatenated as page_scripts.
  5. The page is wrapped by layout/theme.jinja, which emits the design tokens from the global settings as CSS custom properties and slots the header, content, footer, styles, and scripts into the shell.

The editor preview is the same pipeline with is_preview=true: it injects extra preview CSS/JS and can hot-swap a single node’s HTML via Engine::render_node_fragment().

Themes, stores, and persistence

  • Themes live in the repo under themes/ and are loaded at engine startup. The engine is database-free; all data flows in through the DataSources trait.
  • Merchant edits persist in store_templates as JSON config per page_key: _settings (global settings), @root:header-group, @root:footer-group, and page keys like index, product, cart.
  • The editor (storeadmin) writes the same PageConfig shape the renderer reads: a map of node instances keyed by stable UUID-like IDs plus an order array (see Page configs).
Navigation

Type to search…

↑↓ navigate↵ selectEsc close