Template language
This page defines the Handlebars subset implemented by the current Goldar compiler. It is a reference, not a proposal: syntax absent from this page must be treated as unsupported even when another Handlebars implementation accepts it.
The compiler produces a server-renderable View program, a TypeScript typecheck probe, extracted styles, and imported-template dependency metadata from the same semantic model. Any compiler error prevents View-program emission. The recoverable source document, typecheck probe, styles, and diagnostics remain available to editor tooling.
Frontmatter and props
TypeScript frontmatter is optional. When present, it must start at byte zero, use opening and
closing --- delimiters on their own lines, and declare a Props type alias:
---
type Props = {
readonly title: string;
readonly visible: boolean;
};
---
<h1>{{title}}</h1>
Templates without frontmatter keep the broad ambient CompiledTemplate<object> type. A template
with frontmatter but no live type Props declaration is invalid. Text inside TypeScript comments,
quoted strings, or template strings does not count as a declaration or a compile directive.
Goldar passes ordinary frontmatter through to the generated TypeScript declaration and typecheck probe. The only compiler-owned TypeScript statements are relative template imports and their alias calls, described below.
Expressions
Expressions are simple dot-separated paths rooted at Props or at an explicit each binding:
<p>{{account.profile.name}}</p>
Double mustaches HTML-escape the resolved value. The runtime accepts strings, numbers, booleans,
null, and undefined; nullish values render as empty text. Triple mustaches require the branded
TrustedHtml value created by Goldar’s runtime API:
<article>{{{trustedBody}}}</article>
Triple mustaches are an explicit trust boundary. Arbitrary strings are rejected at runtime rather than written as HTML.
Helpers, subexpressions, bracket indexing, implicit this, ../ traversal, @index, and other
Handlebars metadata are not implemented.
Elements and attributes
Goldar emits ordinary HTML elements, doctypes, comments, and text. Non-void literal elements need explicit closing tags. HTML void elements may omit a closing tag and must not be closed explicitly.
Lowercase hyphenated tags are custom-element capabilities. The build integration maps each tag to one local component module unless the tag is a lexically imported template alias. Custom elements are still literal elements and therefore need explicit closing tags.
Attributes support three forms:
<input required>
<p class="summary"></p>
<user-card user={{account}}></user-card>
- A valueless attribute is boolean presence.
- A static value is a quoted or unquoted string and is HTML-escaped during rendering.
- A dynamic value contains exactly one simple path and no surrounding static text.
For a bound attribute, false, null, and undefined omit the attribute; true emits presence;
strings and numbers emit an escaped value. Other runtime values are rejected. Mixed values such as
class="user-{{id}}" are not implemented. Property bindings, event handlers, and action syntax are
also outside the current language.
Conditionals and iteration
The two implemented block forms accept only simple paths and both support {{else}}:
{{#if account.active}}
<p>Active</p>
{{else}}
<p>Inactive</p>
{{/if}}
{{#each accounts as account}}
<p>{{account.name}}</p>
{{else}}
<p>No accounts</p>
{{/each}}
if follows JavaScript truthiness. each requires an array and an explicit identifier. Its body
can resolve the new lexical binding and root props. The empty branch runs in the outer scope.
Nested bindings must not shadow another active binding. unless, implicit-context iteration,
index metadata, and destructured bindings are not implemented.
Outlets
{{outlet}} renders the child views supplied when a compiled template is called. A template may
contain at most one outlet, and the expression is valid only in content:
<main>{{outlet}}</main>
Imported templates
A template may default-import another relative .hbs file and assign it one lowercase,
hyphenated local tag with .as():
---
import AccountCard from "./account-card.hbs";
AccountCard.as("account-card");
type Props = {
readonly account: {
readonly name: string;
};
};
---
<account-card account={{account}} />
Imported-template calls may be self-closing or may contain outlet children. The build integration
resolves their modules, checks for missing dependencies and cycles, and passes the compiled child
templates into dependency slots in lexical order. Imports must be relative, must end in .hbs, and
must have exactly one static alias. goldar-outlet is reserved.
Unlike a literal custom element, an imported-template tag is a server-side template call; it is not reported as a browser component capability.
Template styles
Top-level <style> elements are compile directives and do not render as markup:
<style>
.card { color: rebeccapurple; }
</style>
<article class="card">{{title}}</article>
Goldar scopes ordinary blocks with a deterministic data-goldar-s-* attribute. A valueless
is:global attribute opts out for the complete block, and :global(...) escapes an individual
selector in the Node stylesheet transform. Style directives must be top-level, static,
non-self-closing, and explicitly terminated. Handlebars interpolation and authored
data-goldar-s-* attributes are rejected. Scoped keyframes are not implemented; global keyframes
remain available.
Unsupported syntax
| Area | Not implemented | Current alternative |
|---|---|---|
| Expressions | Helpers, subexpressions, bracket access | Prepare values in application code and pass props |
| Context | this, ../, @index, implicit each context | Use root paths and explicit as name bindings |
| Blocks | unless, custom blocks, destructuring | Use if, each, and explicit props |
| Attributes | Mixed interpolation | Pass one complete value |
| DOM behavior | Events, properties, actions | Use a Custom Element boundary |
| Composition | Multiple outlets or named slots | Compose imported templates or one layout outlet |
| CSS | Scoped keyframes and dynamic CSS | Use global keyframes and CSS custom properties |
Diagnostic catalog
All codes below are compiler or build errors today. An error aborts generated declaration updates and View-program emission; tools may still use recoverable editor artifacts.
| Code | Condition |
|---|---|
GOLDAR1001 | Unterminated opening tag |
GOLDAR1002 | Unterminated HTML comment |
GOLDAR1003 | Unterminated doctype |
GOLDAR1004 | Closing tag without an element name |
GOLDAR1005 | Unterminated closing tag |
GOLDAR1006 | Unterminated Handlebars expression |
GOLDAR1007 | Unexpected closing tag |
GOLDAR1008 | Closing tag does not match the active element |
GOLDAR1009 | Element is missing a closing tag |
GOLDAR1010 | Void element has a closing tag |
GOLDAR1011 | Non-void literal element is self-closing |
GOLDAR1101 | Unsupported, unterminated, or mismatched Handlebars block |
GOLDAR1102 | Helper, subexpression, indexed path, or other complex expression |
GOLDAR1103 | Triple-mustache syntax used as an attribute |
GOLDAR1104 | Event attribute or event modifier |
GOLDAR1105 | Element property binding |
GOLDAR1106 | Template action syntax |
GOLDAR1107 | More than one outlet |
GOLDAR1108 | Implicit context, traversal, metadata, or non-simple path |
GOLDAR1109 | Mixed static and dynamic attribute value |
GOLDAR1110 | Empty Handlebars expression |
GOLDAR1111 | Outlet used in an attribute |
GOLDAR1113 | Nested each binding shadows an active binding |
GOLDAR1200 | Frontmatter opener is not followed by a newline |
GOLDAR1201 | Unterminated frontmatter |
GOLDAR1202 | Frontmatter does not declare a live Props type alias |
GOLDAR1210 | Template import is not a relative .hbs specifier |
GOLDAR1211 | Alias call has no matching default template import |
GOLDAR1212 | Alias is not a static lowercase hyphenated string |
GOLDAR1213 | Alias uses a reserved tag |
GOLDAR1214 | More than one import claims the same alias |
GOLDAR1215 | One imported binding declares more than one alias |
GOLDAR1300 | Style directive is nested |
GOLDAR1301 | Style directive is unterminated |
GOLDAR1302 | Style directive has unsupported attributes |
GOLDAR1303 | Style directive is self-closing |
GOLDAR1304 | Style directive contains Handlebars interpolation |
GOLDAR1305 | Template authors a compiler-reserved scope attribute |
GOLDAR_TEMPLATE_DEPENDENCY_MISSING | An imported template file does not exist |
GOLDAR_TEMPLATE_DEPENDENCY_CYCLE | Imported templates form a cycle |
GOLDAR_COMPONENT_MODULE_MISSING | No local module implements a custom-element tag |
GOLDAR_COMPONENT_MODULE_AMBIGUOUS | More than one local module could implement a tag |
GOLDAR_COMPONENT_NOT_REGISTERED | A compiled module requests an unavailable component |
GOLDAR_STYLE_SYNTAX | The Node CSS transform cannot parse extracted CSS |
GOLDAR_STYLE_KEYFRAMES | A scoped style contains unsupported keyframes |
Numbers not listed in this catalog are not public aliases for another diagnostic.