Attribute order
Tokenizer: no DOM
annealer does not build a DOM or XML tree. It finds each start tag (<tagname … > or />) and splits the attribute list into name/value pairs, keeping only the quote style.
This works the same way for plain HTML and for Vue's non-XML syntax (@click, #default, v-bind:[dynamic]). It also avoids side effects of DOM- or XML-based parsing, such as collapsing whitespace or lowercasing tag and attribute names.
Raw-text elements, comments, {{ }} interpolations, SFC <script> blocks, custom blocks and <template lang="…"> with a non-HTML language are skipped.
Classification
Each attribute is classified by its normalized key:
A profile is a list of groups in output order. Each group has match patterns: an exact name, or a prefix ending in *. An attribute goes to the group whose pattern fits it most specifically: an exact name beats any prefix, and a longer prefix beats a shorter one. Attributes that match nothing go to the single fallback group.
Inside a group, the order is set by sort:
source: as written (default).alphabetical: by key.listed: by the order of thematchpatterns.
HTML profile
Why this order:
- Supplementary attributes come right after
id/class. They describe the element for a human reader.idandclassstay together at the front because that is the common idiom. - ARIA goes near the end. It carries extra metadata for assistive technology that
alt/titlecan't express. It is not the primary description of the element. - ARIA follows WAI-ARIA: role → accessible name → description → properties → states.
data-*is alphabetical because no semantic order exists for it.
Vue profile
The Vue profile follows the default order of eslint-plugin-vue's vue/attributes-order, so its output always satisfies that rule:
Tier 8 is split into other directives first, then other attributes. This matches OTHER_DIRECTIVES → OTHER_ATTR in vue/attributes-order. The HTML sub-ordering applies inside the other-attributes tier.
Same-name clustering
Because classification uses the normalized key, the static and bound forms of one attribute get the same key and stay together. The first occurrence of a key sets the position of its cluster. No special-case logic is needed.
- For
class, the static form goes before the bound form (class="item" :class="{ active }"). - For other names, the cluster keeps the source order. Vue merges
styleand the later declaration wins. For other duplicate names it keeps only one of them. Reordering them could change behavior. v-modelis never clustered with a plain attribute of the same name (value="x" v-model="y"). Its key isv-model, notvalue, and it stays in its own tier because two-way binding is a different concept.
v-bind spreads
v-bind="object" is a boundary. Attributes are never moved across a spread, because that can change which value wins when the object is merged.
The exception is groups marked spreadSafe. These are compile-time directives (is, v-for, v-if/…, v-pre/v-once, custom directives) that never merge with the spread object, so they can be moved across it. Everything else stays inside its segment.
Normalization
The Vue profile enables two rewrites, which run before classification:
directiveShorthand:v-bind:foo→:foo,v-on:click→@click.hyphenateComponentProps: camelCase attribute names on component tags → kebab-case (:itemName→:item-name). Native SVG and MathML elements are not affected.