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.
<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.
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.