Full language support for HTML with Mustache/Handlebars templates
- Syntax Highlighting — Full semantic highlighting for HTML and Mustache, plus embedded JS/TS in
<script>and CSS in<style> - Document Formatting — Auto-format with EditorConfig and config file support
- CLI Linter & Formatter — Check and format templates from the command line
- Document Symbols — Outline view and breadcrumb navigation
- Folding — Collapse HTML elements and Mustache sections
- Hover Information — Tag and attribute documentation
| Syntax | Description |
|---|---|
{{name}} |
Variable interpolation |
{{{html}}} |
Unescaped HTML |
{{#items}}...{{/items}} |
Sections |
{{^items}}...{{/items}} |
Inverted sections |
{{! comment }} |
Comments |
{{> partial}} |
Partials |
Install from the VS Code Marketplace or search for "HTML Mustache" in the Extensions view.
What you get out of the box:
- Syntax highlighting (including embedded JS/TS and CSS)
- Document formatting (format on save, format selection)
- Error diagnostics (parse errors, mismatched tags)
- Document outline and breadcrumbs
- Hover information for HTML tags and attributes
- Code folding for HTML elements and Mustache sections
By default, the extension activates for .mustache, .hbs, and .handlebars files. To use it with .html files, add this to your VS Code settings:
{
"files.associations": {
"*.html": "htmlmustache"
}
}You can also change the language mode for a single file by clicking the language indicator in the status bar and selecting "HTML Mustache".
Install globally or run via npx:
npm install -g @reteps/tree-sitter-htmlmustache
Check templates for parse errors:
htmlmustache check '**/*.mustache' '**/*.hbs'
If include is configured in .htmlmustache.jsonc, patterns are optional:
htmlmustache check
file.mustache:3:3 error: Mismatched mustache section: {{/wrong}}
|
1 | {{#items}}
2 | <li>{{name}}
3 | {{/wrong}}
| ^^^^^^^^^^ Mismatched mustache section: {{/wrong}}
1 error in 1 file (5 files checked)
Detects parse errors, mismatched Mustache sections, mismatched HTML end tags, and missing tokens.
Format templates:
htmlmustache format --write '**/*.mustache'
If include is configured in .htmlmustache.jsonc, patterns are optional:
htmlmustache format --write
Check formatting in CI (exits 1 if any files would change):
htmlmustache format --check 'templates/**/*.hbs'
Read from stdin:
echo '<div><p>hi</p></div>' | htmlmustache format --stdin
Options:
| Flag | Description |
|---|---|
--write |
Modify files in-place (default: print to stdout) |
--check |
Exit 1 if any files would change (for CI) |
--stdin |
Read from stdin, write to stdout |
--indent-size N |
Spaces per indent level (default: 2) |
--print-width N |
Max line width (default: 80) |
--mustache-spaces |
Add spaces inside mustache delimiters |
Skip formatting for specific regions using ignore directives. Both HTML and Mustache comment forms are supported.
Place a comment immediately before the element to preserve its original formatting:
<!-- htmlmustache-ignore -->
<div class="a" id="b">manually formatted</div>{{! htmlmustache-ignore }}
<table>
<tr>
<td>compact</td>
<td>table</td>
</tr>
</table>Only the immediately following sibling node is ignored. Subsequent nodes are formatted normally.
Wrap a region in start/end comments to preserve everything between them:
<!-- htmlmustache-ignore-start -->
<div class="a">content</div>
<p>kept as-is</p>
<!-- htmlmustache-ignore-end -->{{! htmlmustache-ignore-start }} {{#items}}
<li>{{name}}</li>
{{/items}} {{! htmlmustache-ignore-end }}If ignore-start has no matching ignore-end, all remaining siblings in the current scope are preserved as raw text.
Create a .htmlmustache.jsonc file in your project root to configure formatting options. Both the VS Code extension and CLI will pick it up automatically (the file is found by walking up from the formatted file).
The following checks are always enabled and report as errors:
- Syntax errors — invalid or unparseable template syntax
- Missing tokens — e.g. a missing closing
> - Mismatched mustache sections —
{{/wrong}}closing a different section than was opened - Mismatched HTML tags — closing tags that don't match their opening tag, including across mustache branches
- Unclosed HTML tags — non-void elements that are never closed
Additionally, the following rules are configurable. Set their severities ("error", "warning", or "off") in the rules object:
{
"rules": {
"consecutiveDuplicateSections": "off",
"preferMustacheComments": "warning",
},
}| Rule | Default | Description |
|---|---|---|
nestedDuplicateSections |
error |
Flags {{#name}} nested inside another {{#name}} with the same name |
unquotedMustacheAttributes |
error |
Requires quotes around mustache expressions used as attribute values |
consecutiveDuplicateSections |
warning |
Warns when adjacent same-name sections can be merged |
selfClosingNonVoidTags |
error |
Disallows self-closing syntax on non-void HTML elements (e.g. <div/>) |
duplicateAttributes |
error |
Detects duplicate HTML attributes on the same element |
unescapedEntities |
warning |
Flags unescaped & and > characters in text content |
preferMustacheComments |
off |
Suggests replacing HTML comments with mustache comments |
unrecognizedHtmlTags |
error |
Flags HTML tags that are not standard HTML elements or valid custom elements |
Define project-specific lint rules using CSS-like selectors to match HTML elements and Mustache sections:
{
"customRules": [
{
"id": "no-font",
"selector": "font",
"message": "The <font> tag is deprecated. Use CSS instead.",
},
{
"id": "no-inline-styles",
"selector": "[style]",
"message": "Avoid inline styles",
"severity": "warning",
},
{
"id": "images-need-alt",
"selector": "img:not([alt])",
"message": "Images must have alt text for accessibility",
},
{
"id": "no-hidden-inputs-in-list",
"selector": "#items > input[type=hidden]",
"message": "Hidden inputs inside {{#items}} sections are usually a mistake",
},
],
}Each custom rule requires an id, selector, and message. The severity defaults to "error" but can be set to "warning" or "off".
Selector syntax:
| Selector | Matches |
|---|---|
div |
HTML elements by tag name |
#items |
Mustache sections by name ({{#items}}) |
* |
Any HTML element |
# |
Any Mustache section |
div span |
Descendant (span anywhere inside div) |
div > span |
Direct child (span directly inside div) |
[style] |
Attribute presence |
input[type=hidden] |
Attribute value |
img:not([alt]) |
Negated attribute |
div, span |
Comma-separated alternatives |
The > (child) combinator is kind-transparent: div > span matches even if a Mustache section sits between them (e.g. <div>{{#show}}<span>{{/show}}</div>), and #a > #b matches across intervening HTML elements.
Disable a lint rule for an entire file with an inline comment:
<!-- htmlmustache-disable preferMustacheComments -->
{{! htmlmustache-disable selfClosingNonVoidTags }}The comment can appear anywhere in the file. Both built-in and custom rules can be disabled by name/id. Use multiple comments to disable multiple rules.
Both the CLI and VS Code extension respect your .editorconfig file for indentation settings (indent_style, indent_size). EditorConfig values override .htmlmustache.jsonc for indentation, and CLI flags override everything.
Priority order: defaults < .htmlmustache.jsonc < .editorconfig (indent only) < CLI flags
This project is based on tree-sitter-html by Max Brunsfeld and Amaan Qureshi.
{ // File patterns for CLI commands (used when no patterns are passed as arguments) "include": ["**/*.mustache", "**/*.hbs"], // Patterns to always exclude (node_modules and .git are excluded by default) "exclude": ["**/vendor/**"], // Max line width before wrapping (default: 80) "printWidth": 100, // Spaces per indent level (default: 2) "indentSize": 4, // Add spaces inside mustache delimiters: {{ foo }} vs {{foo}} (default: false) "mustacheSpaces": true, // Treat custom tags as raw code blocks (like <script>/<style>) "customCodeTags": [ { "name": "x-code", "languageDefault": "javascript", }, ], }