This document covers component composition in Vuego using the <template include> tag, component shorthands, and the :required attribute for validating component props.
- Component Shorthands
- Basic Component Composition
- Slots
- The Template Tag
- Required Attributes
- YAML Front-Matter for Single File Components
- Complete Examples
Vuego provides a convenient shorthand syntax for using components without needing to write out the full <template include> tag. This allows you to use custom tags that are automatically mapped to component files.
To use component shorthands, enable them when creating your template renderer:
package main
import (
"github.com/titpetric/vuego"
"os"
)
func main() {
root := os.DirFS("templates")
// Enable component shorthands with the default pattern
tpl := vuego.NewFS(root, vuego.WithComponents())
// Or specify custom patterns
tpl := vuego.NewFS(root, vuego.WithComponents("components/*.vuego", "ui/*.vuego"))
}If you call WithComponents() without any arguments, it automatically loads all components from components/*.vuego. Each component file is converted to a kebab-case tag name.
Component shorthands use custom HTML tags that match your component names (converted to kebab-case):
File: components/ButtonPrimary.vuego
<button class="btn-primary">
<slot>Click me</slot>
</button>Usage with shorthand:
<button-primary>Submit</button-primary>Rendered output:
<button class="btn-primary">Submit</button>Usage without content (uses fallback):
<button-primary></button-primary>Rendered output with fallback:
<button class="btn-primary">Click me</button>Equivalent full syntax:
<template include="components/ButtonPrimary.vuego"></template>- Component files are scanned using glob patterns (e.g.,
components/*.vuego) - The filename (without extension) is converted from PascalCase to kebab-case:
ButtonPrimary.vuegobecomes<button-primary>AlertBox.vuegobecomes<alert-box>MyComponent.vuegobecomes<my-component>
- When a shorthand tag is encountered, it's replaced with a
<template include>directive - Attributes on the tag are passed as context variables to the component
Attributes on component shorthand tags are passed as context variables to the component:
components/Badge.vuego:
<template :require="type" :require="text">
<span class="badge badge-{{ type }}">{{ text }}</span>
</template>Usage:
<badge type="success" text="Approved"></badge>Rendered output:
<span class="badge badge-success">Approved</span>You can use custom glob patterns to load components from different directories:
// Load components from multiple directories
tpl := vuego.NewFS(root, vuego.WithComponents(
"components/*.vuego",
"ui/buttons/*.vuego",
"ui/modals/*.vuego",
))With this setup:
- Files in
components/use the format<component-name> - Files in
ui/buttons/use the format<button-name> - Files in
ui/modals/use the format<modal-name>
Vuego allows you to compose reusable components using the <template include> tag. This tag loads and renders another .vuego template file at the specified location.
<template include="path/to/component.vuego"></template>The path is relative to the filesystem root passed to vuego.NewVue().
components/Header.vuego
<template>
<header>
This is the header
</header>
</template>index.vuego
<html>
<head>
<title>My Page</title>
</head>
<body>
<template include="components/Header.vuego"></template>
<main>
Content goes here
</main>
</body>
</html>Slots enable powerful component composition by allowing parent components to provide content to child components. Vuego supports default slots, named slots, and scoped slots.
The simplest form of slot - parent content goes into a single unnamed slot:
components/Card.vuego
<div class="card">
<slot></slot>
</div>Usage:
<card>
<h2>My Card Title</h2>
<p>Card content here</p>
</card>Rendered output:
<div class="card">
<h2>My Card Title</h2>
<p>Card content here</p>
</div>Use multiple slots in a component by giving each a name:
components/Modal.vuego
<div class="modal">
<div class="modal-header">
<slot name="header"></slot>
</div>
<div class="modal-body">
<slot></slot>
</div>
<div class="modal-footer">
<slot name="footer"></slot>
</div>
</div>Usage with v-slot:name syntax:
<modal>
<template v-slot:header>Confirm Action</template>
<template v-slot:footer>
<button>Cancel</button>
<button>Confirm</button>
</template>
Are you sure you want to proceed?
</modal>Shorthand with #name:
<modal>
<template #header>Confirm Action</template>
<template #footer>
<button>Cancel</button>
<button>Confirm</button>
</template>
Are you sure you want to proceed?
</modal>Pass data from the child component to the parent's slot template using :prop="expression" bindings:
components/List.vuego
<ul>
<li v-for="(index, item) in items">
<slot :item="item" :index="index"></slot>
</li>
</ul>Parent receives props:
<list :items="products">
<template v-slot="slotProps">
<strong>{{ slotProps.item.name }}</strong> ({{ slotProps.index }})
</template>
</list>With destructuring:
<list :items="products">
<template v-slot="{ item, index }">
<strong>{{ item.name }}</strong> ({{ index }})
</template>
</list>Combine named slots with scoped props:
components/DataTable.vuego
<table>
<thead>
<slot name="header" :columns="columns"></slot>
</thead>
<tbody>
<tr v-for="row in rows">
<slot name="row" :row="row"></slot>
</tr>
</tbody>
</table>Parent component:
<data-table :columns="cols" :rows="data">
<template #header="{ columns }">
<tr>
<th v-for="col in columns">{{ col }}</th>
</tr>
</template>
<template #row="{ row }">
<td>{{ row.name }}</td>
<td>{{ row.value }}</td>
</template>
</data-table>Provide default content inside a <slot> that renders when no content is provided:
components/Button.vuego
<button class="btn">
<slot>Click me</slot>
</button>Usage:
<!-- With content -->
<button>Submit</button>
<!-- Renders: <button class="btn">Submit</button> -->
<!-- Without content -->
<button></button>
<!-- Renders: <button class="btn">Click me</button> (uses fallback) -->The <template> tag is a wrapper element that gets omitted from the final rendered output. It serves two purposes:
- Wrapping component content - Allows multiple root elements in a component
- Defining component requirements - Using the
:requiredattribute
Without a template tag, you might have multiple root elements which can complicate component structure. The template tag provides a clean wrapper that disappears during rendering.
Without template (valid but less organized):
<h1>Title</h1>
<p>Description</p>With template (recommended):
<template>
<h1>Title</h1>
<p>Description</p>
</template>Both produce the same output, but the template version is clearer about component boundaries.
The :required (or :require) attribute validates that component props are provided when the component is included. This helps catch missing data at render time.
<template :require="propName">
<!-- component content -->
</template>You can specify multiple required attributes:
<template :require="prop1" :require="prop2" :require="prop3">
<!-- component content -->
</template>- When a component is included, attributes are passed as props
- The template validates that all
:requiredprops are present - If a required prop is missing, rendering fails with an error
- Props are available in the component scope for interpolation and binding
components/Button.vuego
<template :require="name" :require="title">
<button :name="name">{{ title }}</button>
</template>Usage:
<template include="components/Button.vuego"
name="submit-btn"
title="Click Me"></template>Rendered output:
<button name="submit-btn">Click Me</button>Error example (missing required prop):
<!-- This will fail because 'name' is missing -->
<template include="components/Button.vuego" title="Click Me"></template>Error: required attribute 'name' not provided
Vuego supports YAML front-matter at the beginning of .vuego files. This allows you to define component data directly in the template file, similar to single-file components (SFCs) in other frameworks.
Place YAML between --- delimiters at the start of your template file:
---
title: Component Title
count: 42
items:
- apple
- banana
---
<template>
<div>
<h1>{{ title }}</h1>
<p>Count: {{ count }}</p>
</div>
</template>- Front-matter is extracted and parsed as YAML
- Variables are automatically added to the template context
- Front-matter values are authoritative - they override any data passed to
Render()orRenderFragment() - Each included component can have its own front-matter
Front-matter is useful for:
- Defining default/static component data
- Storing component metadata
- Simplifying single-purpose components that don't need external data
- Creating self-contained components with built-in defaults
components/Card.vuego
---
backgroundColor: "#f5f5f5"
borderColor: "#ddd"
---
<template>
<div style="background: {{ backgroundColor }}; border: 1px solid {{ borderColor }};">
<slot></slot>
</div>
</template>components/Hero.vuego
---
title: "Welcome to Our Site"
subtitle: "Your tagline here"
cta-text: "Get Started"
background-image: "/images/hero.jpg"
---
<template>
<section class="hero" style="background-image: url({{ background-image }})">
<h1>{{ title }}</h1>
<p>{{ subtitle }}</p>
<button>{{ cta-text }}</button>
</section>
</template>YAML keys with hyphens work directly in templates (e.g., background-image, data-value, aria-label).
When a component is included with attributes, front-matter values take precedence:
components/Config.vuego
---
environment: "production"
debug: false
---
<div>Environment: {{ environment }}</div>Usage with conflicting data:
<template include="components/Config.vuego" environment="development" debug="true"></template>Output:
<div>Environment: production</div>The front-matter values (production, false) override the passed attributes (development, true).
components/Header.vuego
<template>
<header>
<h1>My Website</h1>
<nav>Navigation here</nav>
</header>
</template>components/Footer.vuego
<template>
<footer>
<p>© 2024 My Website</p>
</footer>
</template>index.vuego
<html>
<head>
<title>Home Page</title>
</head>
<body>
<template include="components/Header.vuego"></template>
<main>
<h2>Welcome!</h2>
<p>This is the main content.</p>
</main>
<template include="components/Footer.vuego"></template>
</body>
</html>components/Button.vuego
<template :require="name" :require="title">
<button :name="name">{{ title }}</button>
</template>components/ButtonGroup.vuego
<template>
<div class="button-group">
<template include="Button.vuego"
name="save"
title="Save"></template>
<template include="Button.vuego"
name="cancel"
title="Cancel"></template>
</div>
</template>page.vuego
<template>
<div>
<h2>Form Actions</h2>
<template include="components/ButtonGroup.vuego"></template>
</div>
</template>components/WeatherCard.vuego
<template :require="temperature" :require="location">
<div class="weather-card">
<h3>{{ location }}</h3>
<p class="temp">{{ temperature }}°C</p>
</div>
</template>forecast.vuego
<template>
<h1>Weather Forecast</h1>
<template include="components/WeatherCard.vuego"
location="{{ city }}"
temperature="{{ current.temp }}"></template>
</template>With JSON data:
{
"city": "San Francisco",
"current": {
"temp": "18.5"
}
}Rendered output:
<h1>Weather Forecast</h1>
<div class="weather-card">
<h3>San Francisco</h3>
<p class="temp">18.5°C</p>
</div>-
Always wrap components with
<template>- Even if you have a single root element, it clarifies component boundaries -
Use
:requirefor essential props - If your component cannot function without certain data, mark it as required -
Use front-matter for static data - For components with built-in defaults or static data, use YAML front-matter to keep them self-contained
-
Use relative paths - Keep component paths relative to your template filesystem root for portability
-
Organize components in directories - Use a
components/directory structure for better organization -
Keep components focused - Each component should have a single, clear responsibility
-
Remember front-matter is authoritative - Front-matter values always override passed attributes, so don't use it for optional/overridable data
pages/
├── index.vuego
├── forecast.vuego
└── components/
├── Header.vuego
├── Footer.vuego
├── Button.vuego
└── WeatherCard.vuego
- Data Loading - Automatic config loading from
theme.ymlanddata/ - Theme Structuring Guide - Layout organization and directory conventions
- Template Syntax - Variable interpolation and directives
- FuncMap & Filters - Template functions and custom filters