Four complete sections you can copy into a theme. Each one is a folder in src/sections/ with body.html, schema.json and style.css; tringify theme build compiles it into sections/<name>.vasc. All four pass tringify theme check and render in tringify theme preview.
To try one, copy its folder into your theme, add the section name to the allowed_sections of a page in theme.json (for example the home surface), run tringify theme build, and add the section in the theme editor or in templates/home.json.
Announcement#
A heading, rich text and a button, with an alignment choice. It reads routes so the button can fall back to the shop page.
src/sections/example-announcement/body.html:
<section class="example-announcement example-announcement--{{ settings.alignment }}">
<h2>{{ settings.heading }}</h2>
<div>{{ settings.text }}</div>
{% if settings.button_label -%}
<a class="example-announcement__button" href="{{ settings.button_url | default: routes.shop_url }}">{{ settings.button_label }}</a>
{%- endif %}
</section>
src/sections/example-announcement/schema.json:
{
"name": "example-announcement",
"kind": "section",
"ctx_needs": ["routes"],
"settings": [
{ "id": "alignment", "type": "select", "default": "center",
"options": [{ "value": "left", "label": "Left" }, { "value": "center", "label": "Center" }],
"editor": { "label": "Alignment", "group": "design" } },
{ "id": "heading", "type": "text", "default": "Free shipping this week", "editor": { "label": "Heading", "group": "content" } },
{ "id": "text", "type": "richtext", "default": "<p>On every order, until Sunday.</p>", "editor": { "label": "Text", "group": "content" } },
{ "id": "button_label", "type": "text", "default": "Shop now", "editor": { "label": "Button label", "group": "content" } },
{ "id": "button_url", "type": "url", "default": "", "editor": { "label": "Button link", "group": "content" } }
],
"presets": [{ "name": "Announcement", "category": "Content" }]
}
src/sections/example-announcement/style.css:
.example-announcement { padding: 3rem 1.5rem; }
.example-announcement--center { text-align: center; }
.example-announcement__button { display: inline-block; margin-top: 1.5rem; padding: 0.75rem 1.5rem; background: var(--text); color: var(--bg); text-decoration: none; }
richtext settings hold HTML and print as is. default: gives the button a link when the merchant leaves it empty. Each entry in presets is what the theme editor offers under Add section.
Image with text#
An image beside text, with the image on either side.
src/sections/example-image-with-text/body.html:
<section class="example-image-text example-image-text--{{ settings.image_position }}">
<div class="example-image-text__media">
{% if settings.image and settings.image != empty -%}
<img src="{{ settings.image | image_url: width: 1200 }}" srcset="{{ settings.image | image_srcset: max: 2048 }}" sizes="(min-width: 701px) 50vw, 100vw" alt="{{ settings.image_alt }}" {{ settings.image | image_loading }}>
{%- else -%}
{{- 'lifestyle-1' | placeholder_svg -}}
{%- endif %}
</div>
<div class="example-image-text__copy">
<h2>{{ settings.heading }}</h2>
<div>{{ settings.text }}</div>
</div>
</section>
src/sections/example-image-with-text/schema.json:
{
"name": "example-image-with-text",
"kind": "section",
"settings": [
{ "id": "image_position", "type": "select", "default": "left",
"options": [{ "value": "left", "label": "Image left" }, { "value": "right", "label": "Image right" }],
"editor": { "label": "Image position", "group": "design" } },
{ "id": "image", "type": "image", "editor": { "label": "Image", "group": "content" } },
{ "id": "image_alt", "type": "text", "default": "", "editor": { "label": "Image alt text", "group": "content" } },
{ "id": "heading", "type": "text", "default": "Made to last", "editor": { "label": "Heading", "group": "content" } },
{ "id": "text", "type": "richtext", "default": "<p>Tell the story behind your products.</p>", "editor": { "label": "Text", "group": "content" } }
],
"presets": [{ "name": "Image with text", "category": "Content" }]
}
src/sections/example-image-with-text/style.css:
.example-image-text { display: grid; grid-template-columns: 1fr 1fr; align-items: center; }
.example-image-text--right .example-image-text__media { order: 2; }
.example-image-text__media img, .example-image-text__media svg { width: 100%; height: auto; display: block; }
.example-image-text__copy { padding: clamp(1.5rem, 5vw, 4rem); }
@media (max-width: 700px) { .example-image-text { grid-template-columns: 1fr; } .example-image-text--right .example-image-text__media { order: 0; } }
image_url resizes the merchant's image and image_srcset gives the browser sizes to choose from. image_loading loads the first images on a page at once and the rest lazily. Without an image, placeholder_svg prints artwork so the section never looks broken. See Platform filters.
Product grid#
A grid of products the merchant picks, with sale prices.
src/sections/example-product-grid/body.html:
{% if settings.products.size > 0 %}
<section class="example-products">
<h2>{{ settings.heading }}</h2>
<ul class="example-products__grid" style="--columns: {{ settings.columns }}">
{% for product in settings.products %}
<li>
<a href="{{ product.url }}">
{% if product.featured_image -%}
<img src="{{ product.featured_image.url | image_url: width: 600 }}" alt="{{ product.featured_image.alt_text | default: product.title }}" {{ product.featured_image.url | image_loading }}>
{%- else -%}
{{- 'product-1' | placeholder_svg -}}
{%- endif %}
<h3>{{ product.title }}</h3>
{% if product.price %}
<p>
{% if product.price.on_sale %}<del>{{ product.price.regular | money: isolate: true }}</del>{% endif %}
{{ product.price.current | money: isolate: true }}
</p>
{% endif %}
</a>
</li>
{% endfor %}
</ul>
</section>
{% endif %}
src/sections/example-product-grid/schema.json:
{
"name": "example-product-grid",
"kind": "section",
"settings": [
{ "id": "heading", "type": "text", "default": "Best sellers", "editor": { "label": "Heading", "group": "content" } },
{ "id": "products", "type": "product_list", "max": 8, "editor": { "label": "Products", "group": "content" } },
{ "id": "columns", "type": "range", "min": 2, "max": 4, "step": 1, "default": 4, "editor": { "label": "Columns", "group": "design" } }
],
"presets": [{ "name": "Product grid", "category": "Products" }]
}
src/sections/example-product-grid/style.css:
.example-products { padding: 3rem 1.5rem; }
.example-products__grid { list-style: none; padding: 0; display: grid; gap: 1.5rem; grid-template-columns: repeat(var(--columns, 4), minmax(0, 1fr)); }
.example-products__grid img, .example-products__grid svg { width: 100%; height: auto; display: block; }
.example-products__grid a { color: inherit; text-decoration: none; }
@media (max-width: 700px) { .example-products__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
A product_list setting stores the merchant's picks; the section reads the live products, so titles, images and prices stay current. Print prices with money so they follow the shopper's language and currency, and show price.regular struck through when price.on_sale is true. The section renders nothing until products are picked. See Bound settings and Money and languages.
FAQ with theme blocks#
A list of questions the merchant can add, remove and reorder. Each question is a theme block in its own file, and the section places its blocks with {% blocks %}.
blocks/example-question.vasc:
<details><summary>{{ settings.question }}</summary><div>{{ settings.answer }}</div></details>
{% schema %}
{
"name": "example-question",
"kind": "block",
"settings": [
{ "id": "question", "type": "text", "default": "How long does delivery take?", "editor": { "label": "Question", "group": "content" } },
{ "id": "answer", "type": "richtext", "default": "<p>Most orders arrive in 3 to 5 days.</p>", "editor": { "label": "Answer", "group": "content" } }
]
}
{% endschema %}
src/sections/example-faq/body.html:
{% if settings.blocks != empty %}
<section class="example-faq">
<h2>{{ settings.heading }}</h2>
{% blocks %}
</section>
{% endif %}
src/sections/example-faq/schema.json:
{
"name": "example-faq",
"kind": "section",
"settings": [
{ "id": "heading", "type": "text", "default": "Questions", "editor": { "label": "Heading", "group": "content" } }
],
"blocks": { "max": 20, "types": [], "accept_theme_blocks": true, "accept_app_blocks": false },
"presets": [{ "name": "FAQ", "category": "Content", "blocks": [{ "type": "theme:example-question" }, { "type": "theme:example-question" }] }]
}
src/sections/example-faq/style.css:
.example-faq { padding: 3rem 1.5rem; max-width: 48rem; margin-inline: auto; }
.example-faq details { border-bottom: 1px solid color-mix(in srgb, var(--text) 15%, transparent); padding: 1rem 0; }
.example-faq summary { cursor: pointer; font-weight: 600; }
accept_theme_blocks lets merchants add any theme block to the section; types would list blocks defined inside the section itself. When a template places the section, each block in settings.blocks needs its own id:
{
"type": "example-faq",
"position": 3,
"settings": {
"heading": "Questions",
"blocks": [
{
"id": "faq-delivery",
"type": "theme:example-question",
"settings": {}
},
{
"id": "faq-returns",
"type": "theme:example-question",
"settings": {
"question": "Can I return an item?"
}
}
]
}
}
See Theme blocks for nesting and block settings.