Platform
Learn
Developer docs User guide Quickstart Blog
Company
Services About Contact Links Get started

Site pages and sections

Updated

A site page is a page made of widgets. Its web template declares named sections with render_section, editors fill those sections with widgets, and Raytha renders each widget through its own template. This page covers the template side: sections, the grid, the two functions, and how drafts reach the public site.

The parts

PartWhat it isWho edits it
Site pageA title, a route path, one web template, and a draft and a published copy of its widgets.Editors, in the admin
Web templateLiquid that surrounds the widgets and declares the sections. Layouts and inheritance work as in any other web template.Theme authors
SectionA named slot, such as main or sidebar. Each render_section("name") call in the template is one section.Theme authors
WidgetOne block in a section, with a type, a set of settings, and a position in the 12-column grid.Editors
Widget templateThe Liquid and settings form for a widget type. See Widget templates.Theme authors

A minimal page template

<section class="py-5">
  <div class="container">
    {{ render_section("main") }}
  </div>
</section>

Pick this template for a site page, add a widget to main, and the page renders:

<section class="py-5">
  <div class="container">
    <div class="row">
  <div class="col-md-12">
... the widget's HTML ...
  </div>
</div>
  </div>
</section>

The template is the body of the page. The base layout above it supplies <html>, the header and the footer. A site page template can inherit from a layout like any other template, and render_section can sit in the layout itself.

Raytha ships four site page templates in every theme:

Developer nameSections
raytha_html_page_fullwidthmain
raytha_html_page_sidebarmain (8 columns), sidebar (4 columns)
raytha_html_page_multihero, features, content, cta
raytha_html_homehero, features, content, cta

How sections are found

The admin does not ask you to register sections. When an editor opens a page, Raytha scans the page's template, and every template it inherits from, for render_section("name") and get_section("name") calls. Each distinct name becomes a section the editor can fill.

  • The name must be a quoted string literal, single or double quotes. render_section(my_variable) works at render time but the editor never offers that section.
  • Calls inside {% comment %} blocks and HTML comments <!-- --> are ignored. Mention a section in a comment freely.
  • A template with no calls gets one section named main.
  • Use the same case in the template and on the page. render_section("Main") does not find widgets saved in main.

render_section

render_section("name") returns the HTML of every widget in that section, wrapped in Bootstrap-style rows and columns. A section with no widgets, and a call outside a site page, return an empty string.

Named argumentDefaultEffect
wraptrueWith false, no row or column elements are written and every other option is ignored. Widgets appear in grid order.
row_classrowThe class of each row element.
col_classcol-md-{columnSpan}The class of every column element. Set it and the span is no longer written into the markup.
row_id, col_idnoneAn id attribute on every row or column.
row_attributes, col_attributesnoneExtra attribute text, for example row_attributes="data-aos='fade-up'".
{{ render_section("main", wrap=false) }}
{{ render_section("features", row_class="row g-4", col_class="col-12 col-md-4") }}

The options are applied to every row and column of the call. row_id on a section with three rows writes three elements with the same id.

get_section

When you need markup that render_section cannot produce, use get_section("name"). It returns a list of widgets, ordered by row and then column. Each one has the widget already rendered into content.

MemberValue
id, typeThe widget instance id, and the widget template's developer name.
contentThe widget's rendered HTML. If it failed, an HTML comment naming the widget type and the error.
settingsThe raw settings. Read them as block.settings.headline.
row, column, column_spanNumbers. Rows start at 0.
css_class, html_id, custom_attributesStrings. Empty when the editor set nothing.
is_row_start, is_row_endBooleans. True for the first and last widget of a row.

Rebuild the grid yourself with your own classes:

{% for block in get_section("features") %}
  {% if block.is_row_start %}<div class="feature-row">{% endif %}
  <div class="feature feature-{{ block.type }}" data-span="{{ block.column_span }}">
    {{ block.content }}
  </div>
  {% if block.is_row_end %}</div>{% endif %}
{% endfor %}

Or skip the grid and place the pieces anywhere:

{% assign blocks = get_section("sidebar") %}
{% if blocks.size > 0 %}
  <aside class="sidebar">
    {% for block in blocks %}
      <div class="sidebar-block">{{ block.content }}</div>
    {% endfor %}
  </aside>
{% endif %}

Both functions are defined only while a site page is being rendered. Calling them in a detail or list template returns nothing. Calling them inside a widget template also returns nothing, so widgets cannot nest.

The grid

Every widget has a position in its section:

PropertyRangeMeaning
row0 or moreWidgets with the same row are written inside one row element. Rows are sorted ascending.
column0 to 11The sort order inside a row. It does not become an offset.
columnSpan1 to 12The width, written as col-md-N. The default is 12.
cssClass, htmlId, customAttributestextPassed to the widget as widget.css_class, widget.html_id and widget.custom_attributes. The widget's own template decides whether to print them.

Three cards on one row use row 0 for all of them, column 0, 4 and 8, and columnSpan 4. Two widgets in the same row with a total span under 12 leave the rest of the row empty. The columns do not center themselves.

The default column class is Bootstrap's. If your theme does not load Bootstrap, pass col_class and row_class, or use get_section.

Draft and published

A site page keeps two copies of its widgets. Editors work on the draft. The public site renders the published copy.

  • An unpublished page answers with the 404 template.
  • Saving widgets changes the draft only. The live page changes when the page is published.
  • A signed-in admin who can manage site pages can view the draft by adding ?previewDraft=true to the page's URL. Everyone else gets the published copy and ignores the parameter.
  • The page that is set as the home page cannot be unpublished or deleted. Set another page as the home page first.

Widget templates are different. A change to a widget template applies to the published page at once, because the widget is rendered from its template on every request.

Choosing the template

A page can only use a web template that belongs to the active theme, and it can only place widget types that exist in the active theme. Switching the active theme does not move existing pages. A page that points at the old theme's template keeps rendering with that template's source but looks up widgets in the new theme. After a switch, re-select the template for each page and check every one.

Gotchas

  • The default base layout writes <title> from Target.PrimaryField, which a site page does not have. A site page therefore gets only the organization name as its title. In your own layout, use {% if Target.Title %}{{ Target.Title | escape }} | {% endif %}{{ CurrentOrganization.OrganizationName | escape }}.
  • wrap=false must be the boolean false. The text wrap="false" is a non-empty string and still wraps.
  • A widget that fails to render does not fail the page. Its place in the HTML holds the comment <!-- Widget rendering error: type - message -->. View source after every change. The message can include template text, so do not leave a broken widget on a public page.
  • Removing a section from a template does not remove its widgets. They stay on the page and are not rendered until the section returns.
  • An editor cannot delete a widget template that a page still uses, built-in or not.

Next steps