Skip to content
| Marketplace
Sign in
Visual Studio Code>Formatters>Angular HTML Formatter - Format HTML TemplatesNew to Visual Studio Code? Get it now.
Angular HTML Formatter - Format HTML Templates

Angular HTML Formatter - Format HTML Templates

Fenma

|
38 installs
| (1) | Free
HTML formatter for Angular templates and standard HTML with line-preserving formatting.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Angular HTML Formatter – VS Code Extension

HTML formatter for Angular templates and standard HTML in Visual Studio Code.
Preserves line breaks and formatting for Angular team workflows.

This extension is designed for teams that want predictable indentation everywhere, while only applying stronger formatting rules to tags that are explicitly configured in the repository. It is especially useful for Angular component templates with custom elements such as PrimeNG components like p-select, but it remains safe for ordinary HTML because unknown tags get indent-only behavior by default.

attributeOrder demo

closingStyle demo

attributeLayout multi-line demo

attributeLayout single-line demo

Architecture Summary

The formatter has two main steps:

  • known tags: apply configured tag-specific formatting rules
  • all lines: apply safe indentation without reflow

This keeps unknown tags indentation-only and makes configured component formatting predictable.

Features

  • VS Code formatter for HTML and Angular templates
  • project-based shared formatting config
  • indentation-only behavior for unknown tags
  • configurable tag-specific formatting for known tags
  • Angular-friendly attribute matching
  • Angular interpolation spacing normalization for {{ value }} and pipes such as {{ value | currency }}
  • no reflow or automatic line wrapping

Behavior Rules

Unknown tags

If a tag is not configured in the project config:

  • only indentation is adjusted
  • attribute text stays exactly as written
  • attribute order is untouched
  • closing style is untouched

Known tags

If a tag is configured:

  • attributes are matched and reordered deterministically
  • unknown attributes can be placed first or last depending on config
  • Angular binding syntax is preserved exactly
  • empty elements can be normalized to self-closing or explicit form

Config Reference

Create html-formatter.config.jsonc in your project root:

{
  "indent": {
    "size": 2,
    "useTabs": false
  },
  "contentSafety": {
    "textWhitespace": "strict"             // strict | normalized | off
  },
  "defaultBehavior": {
    "unknownTags": "indent-only" // indent-only
  },
  "knownTagDefaults": {
    "firstLineAttributes": [],
    "attributeOrder": [],
    "unknownAttributesPosition": "last",    // first | last
    "sortUnknownAttributes": "preserve",    // preserve | alphabetical
    "maxAttributeLineWidth": 100,
    "attributeLayout": "preserve",          // preserve | multi-line | single-line
    "closingStyle": "explicit",             // preserve | self-closing | explicit
    "closingBracketPosition": "next-line",  // preserve | same-line | next-line
    "closingTagPosition": "same-line"       // preserve | same-line | next-line
  },
  "tags": {
    "p-select": {
      "firstLineAttributes": [],
      "attributeOrder": [
        "inputId",
        "class",
        "options",
        "placeholder",
        "showClear",
        "optionLabel",
        "optionValue",
        "formControlName"
      ],
      "attributeLayout": "multi-line",
      "closingStyle": "self-closing",
      "closingBracketPosition": "same-line"
    }
  }
}

Top-level settings

indent

Controls indentation for the whole document, including normal HTML nesting and Angular control-flow blocks such as @if {} and @for {}.

{
  "indent": {
    "size": 4,
    "useTabs": false
  }
}

defaultBehavior

Defines the fallback behavior for tags that are not explicitly listed in tags.

{
  "defaultBehavior": {
    "unknownTags": "indent-only"
  }
}

contentSafety

Controls extra safety checks that protect user content while formatting.

{
  "contentSafety": {
    "textWhitespace": "strict"
  }
}

Supported textWhitespace values:

  • "strict": if formatting would change whitespace inside text nodes, the original text is returned
  • "normalized": text-node whitespace may change as a side effect of indentation
  • "off": disables the text-whitespace safety check

Recommended:

  • use "strict" if trust and content preservation are more important than aggressive indentation cleanup

knownTagDefaults

Provides default rule values for tags listed in tags.

If a tag defines its own rule, that tag-specific rule takes priority. If a tag does not define a rule, the value from knownTagDefaults is used.

Supported fields:

  • firstLineAttributes
  • attributeOrder
  • attributeLayout
  • maxAttributeLineWidth
  • unknownAttributesPosition
  • sortUnknownAttributes
  • closingStyle
  • closingBracketPosition
  • closingTagPosition

Example:

{
  "knownTagDefaults": {
    "attributeLayout": "preserve",
    "maxAttributeLineWidth": 100,
    "unknownAttributesPosition": "last",
    "sortUnknownAttributes": "preserve",
    "closingStyle": "explicit",
    "closingBracketPosition": "next-line",
    "closingTagPosition": "same-line"
  }
}

tags

Contains per-tag formatting rules. The key is the tag name, for example p-select, p-inputText or my-component.

Only tags listed here get custom formatting behavior. All other tags remain indent-only.

Example:

{
  "tags": {
    "p-select": {
      "attributeOrder": ["inputId", "class", "options"],
      "closingStyle": "self-closing"
    }
  }
}

Tag rule settings

Each tag under tags can use the following options.

firstLineAttributes

Defines attributes that should stay on the same line as the tag name before the remaining attributes are laid out.

Supported forms:

  • string entries, for example "#dt1"
  • object entries, for example { "name": "picker", "kinds": ["template-ref"] }
  • regex entries, for example { "pattern": "^data-" }

Behavior:

  • attributes listed here are moved directly after the tag name on the first line
  • they are matched in the exact order listed here
  • these attributes do not need to appear in attributeOrder
  • after they are placed, attributeLayout is applied to the remaining attributes
  • with "multi-line", the remaining attributes are placed on following lines
  • with "single-line", the remaining attributes continue on the same line unless width wrapping moves them

Example:

{
  "firstLineAttributes": ["#dt1"]
}

Regex example:

{
  "firstLineAttributes": [{ "pattern": "^#" }]
}

With:

{
  "firstLineAttributes": ["#dt1"],
  "attributeOrder": ["class", "value", "formGroup", "paginator"],
  "attributeLayout": "multi-line"
}

This formats to:

<p-table #dt1
  class="p-datatable-sm"
  [value]="models"
  [formGroup]="formGroup"
  [paginator]="true">

attributeOrder

Defines the preferred attribute order for a known tag.

Supported forms:

  • string entries, for example "optionLabel"
  • object entries, for example { "name": "ngModel", "kinds": ["two-way"] }
  • regex entries, for example { "pattern": "^(aria|data)-" }

Behavior:

  • attributes listed here are moved into this exact order
  • attributes not listed here are treated as unknown attributes
  • unknown attributes are placed according to unknownAttributesPosition
  • the formatter preserves the original attribute text and value
  • standard HTML tags ignore closingStyle; only custom/component tags can use it
  • void tags such as input, img and br also ignore closingTagPosition

Important Angular behavior:

  • "optionLabel" matches both optionLabel and [optionLabel]
  • "options" matches both options and [options]
  • "ngModel" can match [(ngModel)]

Simple example:

{
  "attributeOrder": [
    "inputId",
    "class",
    "options",
    "optionLabel"
  ]
}

Advanced example:

{
  "attributeOrder": [
    { "name": "ngModel", "kinds": ["two-way"] },
    { "name": "options", "kinds": ["property"] },
    { "name": "onChange", "kinds": ["event"] },
    { "name": "ngIf", "kinds": ["structural"] },
    { "name": "picker", "kinds": ["template-ref"] }
  ]
}

Regex example:

{
  "attributeOrder": [
    { "pattern": "^(id|inputId)$" },
    { "pattern": "^(aria|data)-" }
  ]
}

Supported kinds values:

  • plain
  • property
  • event
  • two-way
  • structural
  • template-ref

unknownAttributesPosition

Controls where attributes go that are not listed in attributeOrder.

Supported values:

  • "last": unknown attributes are placed after all configured attributes
  • "first": unknown attributes are placed before all configured attributes

Default:

  • "last"

Example:

{
  "unknownAttributesPosition": "last"
}

attributeLayout

Controls whether known-tag attributes stay in their current layout or are forced onto separate lines. If firstLineAttributes is configured, this setting applies to the remaining attributes after those first-line attributes are placed.

Supported values:

  • "preserve": keep the current single-line or multiline layout
  • "multi-line": place each attribute on its own line under the tag name
  • "single-line": place as many attributes on one line as possible, wrapping only when maxAttributeLineWidth is exceeded

Default:

  • "preserve"

Example:

{
  "attributeLayout": "single-line"
}

maxAttributeLineWidth

Controls the maximum total line width, measured from column 0, when attributeLayout is set to "single-line".

Before the formatter adds the next attribute to the current line, it calculates the resulting width. If that width would exceed this value, the next attribute starts a new line and the same width check is applied again on that line.

Supported values:

  • positive integers, for example 100

Default:

  • null (no width-based wrapping)

Example:

{
  "attributeLayout": "single-line",
  "maxAttributeLineWidth": 100
}

sortUnknownAttributes

Controls how unknown attributes are ordered relative to each other.

Supported values:

  • "preserve": keep their original order
  • "alphabetical": sort unknown attributes alphabetically by normalized attribute name

Default:

  • "preserve"

Example:

{
  "sortUnknownAttributes": "preserve"
}

closingStyle

Controls how the tag is closed.

Supported values:

  • "preserve": keep the current closing style when possible
  • "self-closing": format empty elements as <tag ... />
  • "explicit": format as <tag ...></tag>

Behavior notes:

  • self-closing only collapses a tag if the element is empty or contains whitespace only
  • if the tag contains content, the formatter stays safe and keeps explicit closing
  • standard HTML tags cannot use closingStyle; custom/component tags can still use "self-closing" or "explicit"
  • closingTagPosition cannot be used together with closingStyle: "self-closing" because there is no explicit end tag to place

Examples:

{
  "closingStyle": "self-closing"
}
{
  "closingStyle": "explicit"
}

closingBracketPosition

Controls where the closing > or /> is placed for a known tag.

Supported values:

  • "preserve": keep the existing style when possible, otherwise use the formatter's safe fallback
  • "same-line": put the closing > or /> on the same line as the final attribute
  • "next-line": put the closing > or /> on its own next line

Default:

  • "preserve"

Example with "same-line":

<p-select
  inputId="accountName"
  class="w-full" />

Example with "next-line":

<p-select
  inputId="accountName"
  class="w-full"
/>

closingTagPosition

Controls where the explicit closing tag </tag> is placed when closingStyle is "explicit".

Supported values:

  • "preserve": keep the existing style when possible, otherwise use the formatter's safe fallback
  • "same-line": keep </tag> on the same line as the opening bracket line
  • "next-line": move </tag> to its own next line

Default:

  • "preserve"

Example with "same-line":

<p-select
  inputId="accountName"
  class="w-full"
></p-select>

Example with "next-line":

<p-select
  inputId="accountName"
  class="w-full" >
</p-select>

Legacy compatibility fields

The formatter also accepts these older shorthand fields at the top level:

  • indentSize
  • useTabs

These are normalized internally into:

{
  "indent": {
    "size": 2,
    "useTabs": false
  }
}

Invalid config behavior

If the config is missing or invalid:

  • the formatter does not crash
  • safe defaults are used
  • unknown tags remain indent-only
  • commands such as Validate HTML Formatter Config can help inspect the active config

Attribute Matching

Attribute matching uses the normalized attribute name.

  • inputId matches inputId
  • options matches options and [options]
  • placeholder matches placeholder and [placeholder]
  • ngModel can match [(ngModel)]

If you need more control, attributeOrder also supports objects:

{
  "name": "ngModel",
  "kinds": ["two-way"]
}

Regex-based matching is also supported in both attributeOrder and firstLineAttributes:

{
  "pattern": "^data-",
  "flags": "i"
}

Common regex examples:

  • { "pattern": "^#" } matches template refs such as #dt1 and #picker
  • { "pattern": "^data-" } matches attributes that start with data-
  • { "pattern": "^(id|inputId)$" } matches exactly id or inputId

Supported kinds:

  • plain
  • property
  • event
  • two-way
  • structural
  • template-ref

Angular Interpolations

Text-node interpolations are normalized for readability.

  • {{value}} becomes {{ value }}
  • {{ value }} becomes {{ value }}
  • {{ value|currency }} becomes {{ value | currency }}
  • {{ value|date:'short' }} becomes {{ value | date:'short' }}

Pipe spacing is normalized around top-level | operators.

PrimeNG p-select Example

With this rule:

{
  "attributeOrder": [
    "inputId",
    "class",
    "options",
    "placeholder",
    "showClear",
    "optionLabel",
    "optionValue",
    "formControlName"
  ]
}

this input:

<p-select [showClear]="true" optionValue="id" class="w-full" inputId="accountName" [options]="accountData" [placeholder]="dropdownPlaceholder" optionLabel="code" formControlName="accountName" />

becomes:

<p-select inputId="accountName" class="w-full" [options]="accountData" [placeholder]="dropdownPlaceholder" [showClear]="true" optionLabel="code" optionValue="id" formControlName="accountName" />

Commands

  • Format HTML with Team Formatter
  • Validate HTML Formatter Config
  • Show Active HTML Formatter Config

Make It The Default Formatter

Add this to your workspace settings:

{
  "[html]": {
    "editor.defaultFormatter": "Fenma.angular-team-html-formatter"
  },
  "[angular-html]": {
    "editor.defaultFormatter": "Fenma.angular-team-html-formatter"
  }
}

If your Angular templates are associated with html, this is enough. If your setup uses a separate language id, add the matching override as well.

Local Development

  1. Open this extension project in VS Code.
  2. Run npm install in the integrated terminal.
  3. Run npm test.
  4. Press F5 to start the Extension Development Host.
  5. Open an Angular project with html-formatter.config.jsonc.
  6. Run Format Document or Format Selection.

Build A VSIX

Build:

npm run package:vsix

This creates a .vsix in dist/, for example:

dist/angular-team-html-formatter-0.1.0.vsix

Install it in VS Code via:

  1. Extensions view
  2. ... menu
  3. Install from VSIX...
  4. Select the generated file from dist/

Debugging

  • Use the command Show Active HTML Formatter Config to inspect the resolved config.
  • Enable angularTeamHtmlFormatter.enableDebugLogs in VS Code settings to write debug output to the extension output channel.

Limitations

  • The formatter intentionally avoids full HTML pretty-printing and therefore does not try to normalize every edge case.
  • Range formatting works best when the selection contains complete tags.
  • Inline Angular templates are not implemented yet, but the formatter core is structured so that support can be added later.
  • For safety, self-closing conversion only collapses explicit elements when the content between start and end tags is empty or whitespace-only.

Future Work

  • Inline Angular template support in TypeScript files
  • More configurable multiline attribute layouts
  • Smarter range-format context handling
  • Extension host integration tests
  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2026 Microsoft