Templating Guide
Vitto uses Vento as its templating engine. This guide covers the basics and Vitto-specific features.
Template Syntax
Vento uses a simple and intuitive syntax:
Variables
{{ variableName }} Expressions
{{ 1 + 2 }}
{{ user.name }}
{{ items.length }} Filters (Pipes)
{{ title |> uppercase }}
{{ content |> safe }}
{{ date |> dateFormat("YYYY-MM-DD") }} Comments
{{# This is a comment #}} Layouts
Layouts wrap your page content. The page content is available as the content variable.
Creating a Layout
src/layouts/base.vto:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="{{ metadata.description }}">
{{ set pageTitle = metadata.title ? `${metadata.title} - ${metadata.siteName}` : metadata.siteName }}
<title>{{ pageTitle |> safe }}</title>
{{ renderAssets() |> safe }}
</head>
<body>
{{ include "partials/header.vto" }}
<main>
{{ content |> safe }}
</main>
{{ include "partials/footer.vto" }}
</body>
</html> Nested Layouts
You can nest layouts:
src/layouts/article.vto:
{{ layout "layouts/base.vto" }}
<article>
<header>
<h1>{{ title }}</h1>
<time datetime="{{ date }}">{{ date |> dateFormat }}</time>
</header>
{{ content |> safe }}
</article> Partials
Partials are reusable template components.
Creating a Partial
src/partials/header.vto:
<header>
<nav>
<a href="/">{{ metadata.siteName }}</a>
<a href="/about.html">About</a>
<a href="/blog.html">Blog</a>
</nav>
</header> Including Partials
{{ include "partials/header.vto" }} Passing Data to Partials
{{ include "partials/card.vto" { title: "Hello", content: "World" } }} src/partials/card.vto:
<div class="card">
<h3>{{ title }}</h3>
<p>{{ content }}</p>
</div> Metadata Access
The metadata object configured in your vite.config.ts is automatically available in all templates:
<!DOCTYPE html>
<html>
<head>
<title>{{ metadata.title }}</title>
<meta name="description" content="{{ metadata.description }}">
<meta name="keywords" content="{{ metadata.keywords }}">
{{# Access custom metadata fields #}}
<meta name="author" content="{{ metadata.author }}">
<html lang="{{ metadata.language }}">
</head>
<body>
<h1>Welcome to {{ metadata.siteName }}</h1>
</body>
</html> Tip
Access nested metadata objects directly — metadata.social.twitter, metadata.theme.primaryColor, etc.
Page-Specific Titles
Combine page title with site name:
{{ set pageTitle = metadata.title ? `${metadata.title} - ${metadata.siteName}` : metadata.siteName }}
<title>{{ pageTitle }}</title> Vitto-Specific Features
renderAssets() Function
Injects Vite-generated CSS and JavaScript assets into your template:
<head>
{{ renderAssets() |> safe }}
</head> This automatically generates:
<!-- In development -->
<script type="module" src="/@vite/client"></script>
<script type="module" src="/src/main.js"></script>
<!-- In production -->
<link rel="stylesheet" href="/assets/style-abc123.css" />
<script type="module" src="/assets/main-def456.js"></script> viteAssets Object
Access individual assets manually:
{{# JavaScript #}}
<script type="module" src="{{ viteAssets.main }}"></script>
{{# CSS #}}
{{ for css of viteAssets.css }}
<link rel="stylesheet" href="{{ css }}">
{{ /for }} Dynamic Data from Hooks
Data from hooks is automatically available in templates:
{{# Assuming you have a 'posts' hook #}}
{{ for post of posts }}
<article>
<h2>{{ post.title }}</h2>
<p>{{ post.excerpt }}</p>
</article>
{{ /for }} See Hooks System for more details.
Control Flow
Conditionals
{{ if user }}
<p>Welcome, {{ user.name }}!</p>
{{ else }}
<p>Please log in.</p>
{{ /if }} {{ if items.length > 0 }}
<ul>
{{ for item of items }}
<li>{{ item }}</li>
{{ /for }}
</ul>
{{ else }}
<p>No items found.</p>
{{ /if }} Loops
For…of loop:
{{ for post of posts }}
<h2>{{ post.title }}</h2>
{{ /for }} For…in loop:
{{ for key, value in object }}
<p>{{ key }}: {{ value }}</p>
{{ /for }} With index:
{{ for post, index of posts }}
<p>{{ index + 1 }}. {{ post.title }}</p>
{{ /for }} Filters
Important
Always use the safe filter when rendering HTML content to prevent escaping.
Vento includes built-in filters. Use the safe filter to render HTML:
{{ htmlContent |> safe }} Common filters:
{{ text |> uppercase }}
{{ text |> lowercase }}
{{ text |> trim }}
{{ array |> join(", ") }}
{{ number |> fixed(2) }} Custom Filters
You can add custom filters via ventoOptions:
// vite.config.ts
import { defineConfig } from 'vite';
import vitto from 'vitto';
export default defineConfig({
plugins: [
vitto({
metadata: {
siteName: 'My Site',
title: 'My Site',
},
ventoOptions: {
filters: {
dateFormat: (date: Date, format: string) => {
// Your date formatting logic
return formattedDate;
},
},
},
}),
],
}); Use in templates:
{{ date |> dateFormat("YYYY-MM-DD") }} Best Practices
{{# Always Use safe Filter for HTML Content #}}
{{ content |> safe }}
{{ renderAssets() |> safe }} {{# Escape User-Generated Content #}}
{{# Auto-escaped (safe from XSS) #}}
{{ userComment }}
{{# NOT safe - only use for trusted content #}}
{{ userComment |> safe }} // Leverage Metadata Configuration
vitto({
metadata: {
siteName: 'My Blog',
title: 'My Blog',
description: 'A blog about web development',
author: 'Your Name',
social: {
twitter: '@johndoe',
github: 'johndoe',
},
},
}); Warning
Never use the safe filter on untrusted user input — it bypasses Vento’s XSS protection.
Complete Example
src/pages/blog.vto:
{{ layout "layouts/site.vto" }}
<div class="blog-container">
<h1>{{ metadata.siteName }} Blog</h1>
{{ if posts && posts.length > 0 }}
<div class="posts-grid">
{{ for post of posts }}
{{ include "partials/post-card.vto" {
title: post.title,
excerpt: post.excerpt,
date: post.date,
url: `/blog/${post.slug}.html`
} }}
{{ /for }}
</div>
{{ else }}
<p>No posts available yet.</p>
{{ /if }}
</div> src/partials/post-card.vto:
<article class="post-card">
<h2>
<a href="{{ url }}">{{ title }}</a>
</h2>
<time datetime="{{ date }}">{{ date }}</time>
<p>{{ excerpt }}</p>
<a href="{{ url }}" class="read-more">Read more →</a>
</article> Learn More
- Vento Documentation - Official Vento docs
- Dynamic Routes - Generate pages from data
- Hooks System - Inject dynamic data
Next Steps
- Dynamic Routes - Generate multiple pages from data
- Hooks System - Fetch and inject dynamic data
- Search Integration - Add search to your site