Welcome to Vein

Build storefront themes for Tringify with Vein — a templating engine for components, surfaces, and blocks.

Vein is the templating language for Tringify storefronts. Its markup uses a familiar tag-and-output syntax, so it reads naturally if you've written storefront templates before. You build a store by writing components and arranging them on the pages of your storefront — and the theme editor lets your team configure them visually.

What is a templating language?#

A templating language lets you write a page once and fill it with real data when it loads. You write ordinary HTML for the parts that never change, and add small placeholders for the parts that do — a product's title, its price, today's cart. When the page is shown to a shopper, those placeholders are replaced with that shopper's actual values.

Vein has three things to learn. Output, written with double braces, prints a value. Tags, written with brace-percent, add logic like conditions and loops. Filters, written after a pipe, transform a value before it prints — for example falling back to another value when the first is empty.

liquid
<h1>{{ product.title }}</h1>

{% if product.available_for_sale %}
  <button>Add to cart</button>
{% endif %}

{% for variant in product.variants %}
  <option value="{{ variant.id }}">{{ variant.title }}</option>
{% endfor %}

<p>{{ settings.title | default: shop.name }}</p>

The whole model, in one breath#

A surface (home, product, cart, the header group, …) holds an ordered list of sections. Each section is a component — a Vein file with a schema. A component has settings (its own controls) and slots that hold blocks — and a block is just another component, nested. There is no separate "section" and "block" concept: it is one primitive, the component, repeated and nested for every surface. Learn one and you know them all.

text
surface
  └─ section[]
       └─ component  (a .vein file + {% schema %})
            ├─ settings   (its own controls)
            └─ blocks[]   ← each block is a component, nested
                 └─ component
                      ├─ settings
                      └─ blocks[]   (nests further, same primitive)

Why it works this way#

The schema you write is what drives everything: it generates the controls your team sees in the editor, validates their input, and shapes how the component renders. Define a setting once and it shows up in the editor automatically — what your team configures there is exactly what your store ships.

A component declares what it borrows#

The schema declares more than what a component outputs. It also declares what the component borrows from wherever it is placed: ctx_needs lists the store data it reads (like product or cart), and theme_needs lists the theme settings it relies on. A component can only read what it has declared.

Because a component states its inputs up front, it carries no hidden dependencies. Drop it into any surface, or into another component's slot, and what it needs is known and checkable — which is what lets a component be shared and installed across themes, not just live in the one it was written for.

Start here#