Syntax specification
This document outlines the Jig templating syntax specification. You can reference this doc to understand Jig internals better or create a syntax highlighter for your favorite code editor.
Goals
Jig's primary goal is not to introduce any new dialect to the templating layer. Instead, use JavaScript everywhere.
- Keep the syntax closer to JavaScript.
- It should be easier to type and understand.
- Jig should work with any text format (code, config files, schemas, etc.).
- Generate error stacks pointing to the original source file and the line number.
Primitives
Jig is built on two core primitives:
- Curly braces: The familiar curly braces
{{ }}are used to evaluate a JavaScript expression. - Jig tags: Jig tags start with an
@symbol followed by the tag name. Tags can receive properties and have children elements surrounded by an opening and a closing statement.
Curly braces
Jig uses the familiar curly braces {{ }} for evaluating JavaScript expressions. The output of the expression is concatenated to the output string. For example:
Hello {{ username }}!
Given the username is Virk. The output will be
Hello Virk
Multi-line expressions
Expressions can span across multiple lines. For example:
Hello {{
users.map((user) => {
return user.username
})
}}
Since the output of a template is always a string, the output of a JavaScript expression is also converted to a string.
In the above example, the JavaScript expression returns an array and will be converted to a comma-separated string. The output is the same as writing the following JavaScript code.
String(users.map((user) => {
return user.username
}))
You can self-convert the array to string using the JavaScript Array.join method.
Hello {{
users.map((user) => {
return user.username
}).join(', ')
}}
Escaping curly braces
Suppose you are using Jig to generate code that contains curly braces (e.g., generating templates for other systems). In that case, you may use the @ symbol to inform Jig to skip a given statement. For example:
Jig should not parse @{{ username }}
Jig should not parse {{ username }}
Jig tags
Jig tags are used to add rich features to the templating layer. Features like conditionals, loops, components, and partials are implemented using the tags API.
A tag must be written in its line with no contents around it. This is a conscious decision to keep the tags API easy to scan at the compiler level. You can always mix content + JavaScript code on the same line using curly braces.
Tags are further categorized into the following sub-categories.
Block-level tags
Block-level tags have an opening and a closing statement with content inside it. The @if tag is a block-level tag.
@if(someCondition)
If block content
@end
You can auto-close block-level tags by prefixing the ! operator before the tag name.
@!component('button', { size: 'large' })
@component('button', { size: 'large' })
@end
Inline tags
Inline tags do not accept the body, so you do not have to close them explicitly. The @include tag is an inline tag.
@include('partials/some-file')
Tags without arguments
Tags can be specified without arguments as well. For example, the @debugger tag. However, if a tag accepts arguments, then you must always call it like a function.
@debugger
Swallow newlines
Tags should always be used with block-level content because they create a newline separator between two blocks of text.
However, if you are using tags where newlines can change the output meaning or make it invalid, you must append the ~ symbol to the tag.
Without tilde
Hello
@let(username = 'virk')
{{ username }}
Hello
virk
With tilde
Hello
@let(username = 'virk')~
{{ username }}
Hello virk
Content type suffix
Tags can carry an optional content type suffix that annotates what kind of content their body contains. The suffix is written as : identifier after the tag's closing parenthesis (for seekable tags) or after the tag name (for non-seekable tags).
@if(generateReadme): markdown
# {{ projectName }}
{{ description }}
@end
The suffix is purely metadata — no built-in tag changes behavior based on it. It is available to tag implementations via token.properties.contentType and is used by the VSCode extension to provide syntax highlighting for embedded language blocks.
Valid identifiers start with a letter and may contain letters, digits, _, +, #, -, and .. This covers language names like markdown, ts, c++, c#, objective-c, and es6.proxy.
The suffix can be combined with the tilde (~) to swallow the newline:
@if(x): ts~
const y = 1;
@end
The tilde must come after the identifier. Writing ~ before : (e.g., @if(x)~: ts) is invalid and will raise an error.
Non-seekable tags also support the suffix:
@if(format === 'md')
plain text
@else: markdown
**bold text**
@end
Implicit indentation control
Block tags like @if, @each, @unless, @component, @slot, @pushTo, and @pushOnceTo automatically remove cosmetic indentation from their contents. This ensures the output reflects the logical structure of the data, not the visual nesting of the template source.
The dedent amount is computed as: firstContentLineIndent - tagIndent, clamped to zero.
function render() {
@if(showGreeting)
return "hello"
@end
}
function render() {
return "hello"
}
Without implicit indentation control, the output would contain extra leading whitespace on the return line. Jig strips the excess indentation so output aligns with the tag's position, not the template's visual nesting.
Include tags (@include, @includeIf) handle indentation differently: they indent subsequent lines of the partial's output based on the include tag's column position. This ensures multi-line partial output stays aligned with where the include tag appears.
before
@include('partial')
after
If partial renders as line1\nline2\nline3, the output will be:
before
line1
line2
line3
after
Comments
You can write comments in Jig by wrapping the text inside the {{-- --}} block.
{{-- Inline before --}} Hello {{-- Inline after --}}
{{--
This is a multi-line comment.
--}}
Examples
| Invalid ❌ | Valid ✅ |
|---|---|
|
|
|
|
|
|
| Invalid ❌ | Valid ✅ |
|---|---|
|
|
|
|