Skip to main content

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

html
{{ variableName }}

Expressions

html
{{ 1 + 2 }}
{{ user.name }}
{{ items.length }}

Filters (Pipes)

html
{{ title |> uppercase }}
{{ content |> safe }}
{{ date |> dateFormat("YYYY-MM-DD") }}

Comments

html
{{# 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:

html
<!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:

html
{{ 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:

html
<header>
  <nav>
    <a href="/">{{ metadata.siteName }}</a>
    <a href="/about.html">About</a>
    <a href="/blog.html">Blog</a>
  </nav>
</header>

Including Partials

html
{{ include "partials/header.vto" }}

Passing Data to Partials

html
{{ include "partials/card.vto" { title: "Hello", content: "World" } }}

src/partials/card.vto:

html
<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:

html
<!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:

html
{{ 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:

html
<head>
  {{ renderAssets() |> safe }}
</head>

This automatically generates:

html
<!-- 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:

html
{{# 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:

html
{{# 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

html
{{ if user }}
  <p>Welcome, {{ user.name }}!</p>
{{ else }}
  <p>Please log in.</p>
{{ /if }}
html
{{ 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:

html
{{ for post of posts }}
  <h2>{{ post.title }}</h2>
{{ /for }}

For…in loop:

html
{{ for key, value in object }}
  <p>{{ key }}: {{ value }}</p>
{{ /for }}

With index:

html
{{ 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:

html
{{ htmlContent |> safe }}

Common filters:

html
{{ text |> uppercase }}
{{ text |> lowercase }}
{{ text |> trim }}
{{ array |> join(", ") }}
{{ number |> fixed(2) }}

Custom Filters

You can add custom filters via ventoOptions:

ts
// 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:

html
{{ date |> dateFormat("YYYY-MM-DD") }}

Best Practices

html
{{# Always Use safe Filter for HTML Content #}}
{{ content |> safe }}
{{ renderAssets() |> safe }}
html
{{# Escape User-Generated Content #}}
{{# Auto-escaped (safe from XSS) #}}
{{ userComment }}

{{# NOT safe - only use for trusted content #}}
{{ userComment |> safe }}
ts
// 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:

html
{{ 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:

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

Next Steps