tempo
A lightweight CLI for managing assets and scaffolding components in templ-based projects.
tempo is a lightweight CLI for managing assets and scaffolding components in templ-based projects. Inspired by the Italian word for "time", it streamlines CSS & JS workflows while preserving a smooth developer experience.

π Table of Contents
β¨ Features
- Automated CSS & JS management β Keeps styles and scripts in separate files while seamlessly injecting them into
.templ components.
- Structured asset workflow β Ensures a clean, maintainable approach to managing CSS and JS within
templ projects.
- Fast, lightweight component scaffolding β Quickly generate components and variants with predefined templates and actions.
- Extensible templating system β Supports custom function providers (local or remote) to enhance
tempo's capabilities.
π‘ Motivation
While building a UI component library in Golang with templ, I deliberately chose to use plain CSS and vanilla JavaScript. This decision introduced two key challenges:
The Problem
- Managing CSS & JS assets β While
templ excels at Go/HTML templating, it lacks a structured approach for handling standalone styles and scripts. Although you can write CSS and JS directly within .templ files, this comes at the cost of losing native tooling benefits such as syntax highlighting, formatting, and autocompletion. As a result, maintaining styles and scripts efficiently while keeping them separate from .templ files required a better workflow.
- Scaffolding new components β Every component followed the same folder structure, but manually copying files and folders was inefficient. I initially used Plop.js, but it required setting up a full Node.js project.
The Solution
tempo solves both problems natively in Go, eliminating the need for Node.js while providing a structured, opinionated workflow for component and asset management.
β¨ Preserving the Developer Experience
With tempo, CSS and JS files remain untouched during development, allowing developers to continue using their preferred tools:
- Linters & formatters (e.g., Prettier, ESLint, Stylelint)
- IDE features like syntax highlighting, error detection, and inline code suggestions (e.g., in VSCode).
- Existing workflows remain intactβensuring a familiar, efficient experience.
When you run tempo, CSS and JS files are injected into .templ components automatically, but the original source files remain unchanged. This approach preserves developer productivity while enabling seamless integration into templ-based projects.
π» Installation
go install
Requires Go v1.23+
go install github.com/indaco/tempo@latest
Manually
Download the pre-compiled binaries from the releases page and place it in a directory available in your system's PATH.
π Usage
To start using tempo, initialize your project and define your components. Below are the key steps:
1. Initialize tempo
tempo init
Generates a tempo.yaml configuration file. Customize it to fit your project. See the Configuration section for details.
2. Define a Component or Variant
tempo component define
tempo variant define
Generates templates for components/variants inside .tempo-files/templates/ along with an action JSON file inside .tempo-files/actions/. See the Templates & Actions section for details.
3. Create a Component
tempo component new --name button
Creates a new component:
assets/button/ β Holds CSS and JS files.
components/button/ β Contains .templ and .go file
4. Sync Assets with Components
tempo sync
Injects CSS & JS into .templ files using guard markers.
πΉ Example mapping:
assets/button/css/button.css β components/button/css/button.templ
assets/button/css/variant/outline.css β components/button/css/variant/outline.templ
π οΈ CLI Commands & Options
NAME:
tempo - A lightweight CLI for managing assets and scaffolding components in templ-based projects.
USAGE:
tempo <subcommand> [options] [arguments]
VERSION:
v0.1.0
DESCRIPTION:
tempo simplifies asset management in templ-based projects, providing a seamless workflow for
handling CSS and JS files. It automatically extracts and injects styles and scripts into .templ
components while preserving the original source files, ensuring a smooth developer experience.
Additionally, it offers a lightweight scaffolding system to quickly generate component and variant
templates with predefined structures.
COMMANDS:
init Initialize a Tempo project
component Define component templates and generate instances from them
variant Define variant templates and generate instances from them
register Register is used to extend tempo
sync Process & sync asset files into templ components
help Shows a list of commands or help for one command
GLOBAL OPTIONS:
--help, -h show help
--version, -v print the version
init
Initialize a tempo project by generating a tempo.yaml configuration file. This file allows you to customize how components and assets are generated and managed.
For a full list of configuration options, See the Configuration section for details.
component
Define reusable component templates and generate instances from them.
Define a Component
tempo component define
Creates a scaffold for defining a UI component, including template files and an action JSON file.
These definitions are later used by tempo component new to generate real components.
Flags (tempo component define)
--force
- Force overwriting if already exists (default: false)
--dry-run
- Preview actions without making changes (default: false)
--js
- Whether or not JS is needed for the component (default: false)
Generate a Component
tempo component new --name button
Uses the templates and actions created with tempo component define to generate a real component inside assets/ and components/.
Example: Running tempo component new --name dropdown will generate:
assets/dropdown/ (CSS & JS files)
components/dropdown/ (with .templ and .go files)
Flags (tempo component new)
--package (-p) value
- The Go package name where components will be generated (default: components)
--assets (-a) value
- The directory where asset files (e.g., CSS, JS) will be generated (default: assets)
--name (-n) value
- Name of the component
--js
- Whether or not JS is needed for the component (default: false)
--force
- Force overwriting if the component already exists (default: false)
--dry-run
- Preview actions without making changes (default: false)
variant
Define component variant templates and create instances based on them.
Define a Variant
tempo variant define
Creates a scaffold for defining a variant of an existing component (e.g., an outline button variant), including template files and an action JSON file.
These definitions are later used by tempo variant new to generate real component variants.
Flags (tempo variant define)
--force
- Force overwriting if the variant definition already exists (default: false)
--dry-run
- Preview actions without making changes (default: false)
Generate a Variant
tempo variant new --name outline --component button
Uses the templates and actions created with tempo variant define to generate a real instance of a variant inside the corresponding component folder.
Example: Running tempo variant new --name outline --component button will generate:
components/button/variant/outline.templ
assets/button/css/variant/outline.css
Flags (tempo variant new)
--package (-p) value
- The Go package name where components will be generated (default: components)
--assets (-a) value
- The directory where asset files (e.g., CSS, JS) will be generated (default: assets)
--name (-n) value (required)
- The name of the variant being generated
--component (-c) value (required)
- Name of the component or entity to which this variant belongs
--force
- Force overwriting if the variant already exists (default: false)
--dry-run
- Preview actions without making changes (default: false)
register
Extend tempo with the register command.
tempo register -h
Register a function provider
Enhance tempoβs templating capabilities by adding custom function providers. You can register a provider from either a local Go module or a remote Git repository.
tempo register functions --name sprig --url https://github.com/indaco/tempo-provider-sprig.git
Flags (tempo register functions)
--name (-n) value
- Name for the function provider
--url (-u) value
- Repository URL
--path (-p) value
- Path to a local go module provider
sync
Automatically sync CSS and JS assets with .templ components.
tempo sync
The sync command scans the input folder for CSS and JS files, then injects their content into the corresponding .templ files in the output folder:
- Extracts CSS and JS from source files.
- Injects them into
.templ files inside sections marked by guard markers.
- Keeps components up to date without manual copying.
Whenever you update your CSS or JS files, simply run tempo sync to propagate the changes.
Flags
--input value
- The directory containing asset files (e.g., CSS, JS) to be processed (default: assets)
--output value
- The directory containing the `.templ` component files where assets will be injected (default: components)
--exclude value
- Subfolder (relative to input directory) to exclude from the processing
--workers value
- Number of concurrent workers processing asset files (default: numCPUs * 2)
--prod
- Enable production mode, minifying the injected content (default: false)
--force
- Force processing of all files, ignoring modification timestamps (default: false)
--summary (-s) value
- Specify the summary format:
text, json, or none (default: "text")
--verbose
- Show detailed information in the summary report (default: false)
--report-file (--rf) value
- Export the summary to a JSON file
[!TIP]
Live Reload: If you're using a live reload tool like air, templier, or watchexec, pass --summary none to reduce unnecessary output.
[!NOTE]
Want to use tempo without scaffolding?
Check out Using tempo sync as a Standalone Command.
β‘ Using tempo sync as a Standalone Command
The sync command can be used independently of the scaffolding flow (define, new, register). All it requires is:
- A valid
tempo.yaml file.
- A project folder structure matching the paths defined in
tempo.yaml.
- Guard markers in your
.templ files to specify where assets should be injected.
This makes tempo sync a great option for developers who simply want to synchronize CSS and JS assets with their .templ files, without using the full scaffolding features.
π₯ Use Cases for Standalone sync
- Already have components but want automated asset injection.
- Only need asset handling without using the full scaffolding flow.
- Maintain existing folder structures while benefiting from tempoβs synchronization features.
π οΈ Example Standalone Workflow
Start by creating a Go module using the go mod init command.
-
Initialize tempo.yaml:
tempo init
-
Set up your folders:
.
βββ assets/
β βββ button/
β βββ button.css
βββ components/
βββ button/
βββ button.templ
-
Prepare .templ file with guard markers:
package button
var buttonCSSHandle = templ.NewOnceHandle()
templ Button() {
@buttonCSSHandle.Once() {
<style type="text/css">
/* [tempo] BEGIN - Do not edit! This section is auto-generated. */
/* [tempo] END */
</style>
}
-
Run the sync command:
tempo sync
-
Result:
CSS and JS are injected into the corresponding .templ files, replacing the content between the guard markers.
π Guard Markers Explained
tempo sync uses guard markers in .templ files to locate where CSS and JS should be injected.
By default, it looks for the following markers:
/* [tempo] BEGIN - Do not edit! This section is auto-generated. */
/* [tempo] END */
Only the text inside square brackets ([tempo]) can be customized in your tempo.yaml file under the templates.guard_marker setting.
[!IMPORTANT]
If no guard markers are present, tempo sync will skip the file, ensuring only intended sections are updated.
βοΈ Configuration
π View Default tempo.yaml Configuration
# The root folder for Tempo files
tempo_root: .tempo-files
app:
# The name of the Go module being worked on.
go_module: <FROM_GO.MOD>
# The Go package name where components will be organized and generated.
go_package: components
# The directory where asset files (CSS, JS) will be generated.
assets_dir: assets
# Indicates whether JavaScript is required for the component.
# with_js: false
# The name of the CSS layer to associate with component styles.
# css_layer: components
processor:
# Number of concurrent workers (numCPUs * 2).
workers: 4
# Summary format: long, compact, json, none.
summary_format: compact
templates:
# A text placeholder or sentinel used in template files to mark auto-generated content.
guard_marker: tempo
# File extensions used for template files.
extensions:
- .gotxt
- .gotmpl
- .tpl
Configuration Options
Project Structure
| Key |
Default |
Description |
tempo_root |
.tempo-files |
Root folder where tempo stores its templates and actions. |
Application-Specific Settings
| Key |
Default |
Description |
app.go_module |
|
Name of the Go module being worked on read from go.mod. |
app.go_package |
components |
Go package name where components will be generated. |
app.assets_dir |
assets |
Directory where asset files (CSS, JS) will be generated. |
app.with_js |
false |
Whether JavaScript is required for the component. |
app.css_layer |
components |
CSS layer name to associate with component styles. |
Files Processing Options
| Key |
Default |
Description |
processor.workers |
4 |
Number of concurrent workers. |
processor.summary_format |
long |
Summary format: long, compact, json, none. |
Templates Options
| Key |
Default |
Description |
templates.guard_marker |
tempo |
Placeholder used in templates to mark auto-generated content. |
templates.extensions |
.gotxt, .gotmpl, .tpl |
File extensions used for template files. |
templates.function_providers |
[] (empty) |
A list of external function providers that can be loaded from a local path or a remote URL. |
Each function provider entry follows this format:
templates:
function_providers:
- name: default
type: path
value: ./providers/default
- name: custom
type: url
value: https://github.com/user/custom-provider.git
Fields
| Field |
Type |
Description |
name |
string |
(Optional) The name of the function provider. |
type |
string |
Specifies if the provider is a local path or a remote url (Git repo). |
value |
string |
The actual path or URL where the provider is located. |
π οΈ Example Use Case
If your project follows a custom structure, you can update tempo.yaml like this:
app:
go_module: myproject
package: ui/components
assets_dir: static/assets
with_js: true
css_layer: my-layer
This ensures:
- Assets are stored in
static/assets
- Components are generated under
ui/components
- JavaScript support is enabled
- CSS styles are associated with the
my-layer layer
ποΈ Templates & Actions
π The tempo component define and tempo variant define commands generate:
- Templates stored in
.tempo-files/templates/, using Goβs text/template.
- Actions defined in
.tempo-files/actions/, specifying file and folder creation in JSON format.
Templates
Templates define the structure of components and variants. They use Goβs text/template engine along with custom template functions provided by Tempo.
π Default Template Functions
Tempo provides a set of built-in helper functions:
| Function |
Description |
goPackageName |
Converts a string into a valid Go package name. |
goExportedName |
Converts a string into a valid exported Go function name. |
goUnexportedName |
Converts a string into a valid unexported Go function name. |
normalizePath |
Normalizes a path string. |
isEmpty |
Checks if a string is empty. |
π Built-in Template Variables
Tempo automatically provides a set of predefined variables that can be used inside templates. These variables come from the configuration and CLI context during execution.
| Variable |
Description |
TemplatesDir |
The root directory containing template files. |
ActionsDir |
The root directory containing actions files. |
GoModule |
The name of the Go module being worked on. |
GoPackage |
The Go package name where components will be organized and generated. |
ComponentName |
The name of the component being generated. |
VariantName |
The name of the variant being generated (if applicable). |
AssetsDir |
The directory where asset files (CSS, JS) will be generated. |
WithJs |
Whether JavaScript is required for the component (true/false). |
CssLayer |
The CSS layer name associated with component styles. |
GuardMarker |
Placeholder used in templ files to mark auto-generated sections. |
π Extending Template Functions
Tempo supports external function providers, allowing you to integrate additional helper functions into your templates.
See the full guide in Extending Tempo.
Actions
Actions define how templates should be processed. They are stored in .tempo-files/actions/ as JSON files.
Example component action file (component.json):
[
{
"item": "file",
"templateFile": "component/templ/component.templ.gotxt",
"path": "{{ .GoPackage }}/{{ .ComponentName | goPackageName }}/{{ .ComponentName | goPackageName }}.templ"
},
{
"item": "file",
"templateFile": "component/assets/css/base.css.gotxt",
"path": "{{ .AssetsDir }}/{{ .ComponentName | goPackageName }}/css/base.css"
},
{
"item": "folder",
"source": "component/assets/css/themes",
"destination": "{{ .AssetsDir }}/{{ .ComponentName | goPackageName }}/css/themes"
}
]
Each object defines a templating action:
| Key |
Type |
Description |
item |
file/folder |
Whether the action is creating a file or a folder. |
templateFile |
string |
Path to the template file (only for file items). |
path |
string |
Output path for the generated file. |
source |
string |
Source folder (only for folder items). |
destination |
string |
Destination folder for copied content. |
skipIfExists |
bool |
Whether to skip the file if it already exists. |
force |
bool |
Whether to overwrite existing files. |
[!NOTE]
When item is folder, all files inside the source folder will be processed and copied to the destination folder.
Variant actions (variant.json) follow the same structure but target a specific componentβs variant subfolder.
π Extending tempo
tempo provides two ways to extend how templates work:
- Custom template variables β enrich templates with additional context using
user_data.
- Custom template functions β bring in new helpers via external function providers.
π§ Adding Custom Template Variables
You can pass additional variables to your templates using the user_data section in the tempo.yaml config file. These variables are accessible inside templates using .UserData.
Example: tempo.yaml
# ....
templates:
user_data:
author: Jane Doe
year: 2025
config:
option1: true
option2: false
Basic Access (dot notation)
Author: {{ .UserData.author }}
Year: {{ .UserData.year }}
Nested Access
You can use either:
index(built-in from text/template)
lookup(provided by tempo, supports dot notation)
{{ index (index .UserData "config") "option1" }}
{{ lookup .UserData "config.option1" }}
π¦ Using External Function Providers
You can add external function providers in two ways:
1. Via the tempo.yaml configuration file:
templates:
function_providers:
- name: sprig
type: url
value: https://github.com/indaco/tempo-provider-sprig.git
2. Via the CLI using the tempo register functions command:
-
Register a GitHub repository provider:
tempo register functions --name sprig --url https://github.com/indaco/tempo-provider-sprig.git
-
Register a local provider from a directory:
tempo register functions --name myprovider --path /path/to/myprovider_module
π Implementing a Custom Template Function Provider
To create a custom function provider, implement the TemplateFuncProvider interface from the github.com/indaco/tempo-api.
See tempo api for full details.
Example: Creating a Custom Function Provider
package myprovider
import (
"text/template"
"github.com/indaco/tempo-api/templatefuncs"
)
// MyProvider implements the TemplateFuncProvider interface
type MyProvider struct{}
// GetFunctions returns a map of function names to implementations
func (p *MyProvider) GetFunctions() template.FuncMap {
return template.FuncMap{
"myFunc": func() string { return "Hello from myFunc!" },
}
}
// Provider instance
var Provider templatefuncs.TemplateFuncProvider = &MyProvider{}
Once your provider is implemented:
-
If published as a Git repository, register it using:
tempo register functions --name myprovider --url https://github.com/user/myprovider_module.git
-
If stored locally, register it using:
tempo register functions --name myprovider --path /path/to/myprovider_module
π¦ Available Function Providers
A pre-built function provider for Masterminds/sprig is available for convenience:
tempo register functions --name sprig --url https://github.com/indaco/tempo-provider-sprig.git
This allows you to access Sprig functions within Tempo templates.
π€ Contributing
Contributions are welcome!
See the Contributing Guide for setting up the development tools.
π License
This project is licensed under the MIT License - see the LICENSE file for details.