Beyond HTML
Markuplint can lint syntaxes beyond HTML — including JSX, Vue, Svelte, Pug, PHP, and more — by using parser and spec plugins.
Installing plugins
Install the parser plugin through your package manager:
- npm
- Yarn
- pnpm
- Bun
npm install -D @markuplint/pug-parser
yarn add --dev @markuplint/pug-parser
pnpm add -D @markuplint/pug-parser
bun add --dev @markuplint/pug-parser
If your code uses tagged template literals containing HTML (e.g., lit-html), install the tagged template literal parser:
- npm
- Yarn
- pnpm
- Bun
npm install -D @markuplint/tagged-template-literal-parser
yarn add --dev @markuplint/tagged-template-literal-parser
pnpm add -D @markuplint/tagged-template-literal-parser
bun add --dev @markuplint/tagged-template-literal-parser
If a syntax has its own specification you should install the spec plugin with the parser plugin:
- npm
- Yarn
- pnpm
- Bun
npm install -D @markuplint/jsx-parser @markuplint/react-spec
yarn add --dev @markuplint/jsx-parser @markuplint/react-spec
pnpm add -D @markuplint/jsx-parser @markuplint/react-spec
bun add --dev @markuplint/jsx-parser @markuplint/react-spec
- npm
- Yarn
- pnpm
- Bun
npm install -D @markuplint/vue-parser @markuplint/vue-spec
yarn add --dev @markuplint/vue-parser @markuplint/vue-spec
pnpm add -D @markuplint/vue-parser @markuplint/vue-spec
bun add --dev @markuplint/vue-parser @markuplint/vue-spec
Supported syntaxes
| Template or syntax | Parser | Spec |
|---|---|---|
| JSX | @markuplint/jsx-parser | @markuplint/react-spec |
| Vue | @markuplint/vue-parser | @markuplint/vue-spec |
| Svelte | @markuplint/svelte-parser | @markuplint/svelte-spec |
| SvelteKit | @markuplint/svelte-parser/kit | - |
| Astro | @markuplint/astro-parser | - |
| Alpine.js | @markuplint/alpine-parser | @markuplint/alpine-spec |
| HTMX | - | @markuplint/htmx-spec |
| Tagged template literals (lit-html etc.) | @markuplint/tagged-template-literal-parser | - |
| Markdown | @markuplint/markdown-parser | - |
| MDX | @markuplint/mdx-parser | @markuplint/react-spec |
| Pug | @markuplint/pug-parser | - |
| PHP | @markuplint/php-parser | - |
| Smarty | @markuplint/smarty-parser | - |
| eRuby | @markuplint/erb-parser | - |
| EJS | @markuplint/ejs-parser | - |
| Mustache or Handlebars | @markuplint/mustache-parser | - |
| Nunjucks | @markuplint/nunjucks-parser | - |
| Liquid | @markuplint/liquid-parser | - |
There is @markuplint/html-parser package but the core package includes it.
You don't need to install and to specify it to the configuration.
It's not able to support syntaxes if one's attribute is complex.
✅ Available code
<div attr="{{ value }}"></div>
<div attr='{{ value }}'></div>
<div attr="{{ value }}-{{ value2 }}-{{ value3 }}"></div>
❌ Unavailable code
If it doesn't nest by quotations.
<div attr={{ value }}></div>
PULL REQUEST WANTED: This problem is recognized by developers and created as an issue #240.
Applying plugins
Specify the plugin in the parser property of your configuration file. If the syntax has a spec plugin, add it to the specs property as well. Use a regular expression as the key to match target file names.
{
"parser": {
"\\.jsx$": "@markuplint/jsx-parser"
},
"specs": {
"\\.jsx$": "@markuplint/react-spec"
}
}
{
"parser": {
"\\.vue$": "@markuplint/vue-parser"
},
"specs": {
"\\.vue$": "@markuplint/vue-spec"
}
}
{
"parser": {
"\\.ts$": "@markuplint/tagged-template-literal-parser"
}
}
{
"parser": {
"\\.md$": "@markuplint/markdown-parser"
}
}
{
"parser": {
"\\.mdx$": "@markuplint/mdx-parser"
},
"specs": {
"\\.mdx$": "@markuplint/react-spec"
}
}
See the parser and specs property references for details.
Why need the spec plugins?
For example, the key attribute doesn't exist in native HTML elements, but it's commonly used in React and Vue. Spec plugins like @markuplint/react-spec and @markuplint/vue-spec tell Markuplint about these framework-specific attributes.
const Component = ({ list }) => {
return (
<ul>
{list.map(item => (
<li key={item.key}>{item.text}</li>
))}
</ul>
);
};
<template>
<ul>
<li v-for="item in list" :key="item.key">{{ item.text }}</li>
</ul>
</template>
In addition, spec plugins include definitions for framework-specific attributes and directives.
Pretenders
In React, Vue, and more, custom components cannot be evaluated as HTML elements. This means Markuplint's content model rules — such as permitted-contents — have no way of knowing what a component actually renders. Without this information, a <Button> component that renders a <button> element is treated as an unknown element, and invalid nesting like <a><Button /></a> (interactive content inside interactive content) goes undetected.
<List>{/* No evaluate as native HTML Element */}
<Item />{/* No evaluate as native HTML Element */}
<Item />{/* No evaluate as native HTML Element */}
<Item />{/* No evaluate as native HTML Element */}
</List>
The Pretenders feature resolves that by telling Markuplint what each component renders as.
Manual configuration
You can manually specify a selector for each component and the HTML element it renders:
{
"pretenders": [
{
"selector": "List",
"as": "ul"
},
{
"selector": "Item",
"as": "li"
}
]
}
<List>{/* Evaluate as <ul> */}
<Item />{/* Evaluate as <li> */}
<Item />{/* Evaluate as <li> */}
<Item />{/* Evaluate as <li> */}
</List>
This works well for small projects, but manually maintaining the list becomes tedious as your component library grows. That's where dynamic scanning comes in.
See the details of pretenders property on the configuration if you want.
Dynamic scanning
This feature is experimental and may change in future releases.
Instead of manually listing every component, you can let Markuplint scan your component source files and discover pretender mappings automatically.
{
"pretenders": {
"scan": [
{
"files": "./src/components/**/*.tsx"
}
]
}
}
This single configuration replaces what might otherwise be dozens of manual pretender entries. When Markuplint runs, it analyzes your component files and determines:
- Which HTML element each component renders as its root element
- Whether the component accepts children (slots detection)
- Static attributes on the root element
Supported file types
File extensions determine the scanner automatically:
| Extensions | Scanner | Frameworks |
|---|---|---|
.js, .jsx, .ts, .tsx | JSX scanner | React, Preact, Solid, etc. |
.vue | Template scanner | Vue |
.svelte | Template scanner | Svelte |
.astro | Template scanner | Astro |
You can scan multiple file types at once:
{
"pretenders": {
"scan": [
{
"files": "./src/components/**/*.tsx"
},
{
"files": "./src/components/**/*.vue",
"ignoreComponentNames": ["BaseLayout"]
}
]
}
}
What the scanner detects
Consider the following React component:
const ProfileCard = ({ children }) => {
return <article className="profile">{children}</article>;
};
The scanner automatically discovers that ProfileCard renders as <article> and accepts children. This is equivalent to writing:
{
"selector": "ProfileCard",
"as": {
"element": "article",
"slots": true
}
}
Now Markuplint can correctly validate that <ProfileCard> contains only flow content (as <article> does), and that nesting <ProfileCard> inside a <p> would be invalid.
Combining scan with manual definitions
You can use scan alongside manual data definitions. This is useful when the scanner cannot determine the correct mapping for a particular component, or when you want to override the scanned result:
{
"pretenders": {
"scan": [
{
"files": "./src/components/**/*.tsx"
}
],
"data": [
{
"selector": "SpecialComponent",
"as": {
"element": "nav",
"aria": { "name": { "fromAttr": "label" } }
}
}
]
}
}
See pretenders.scan for the full configuration reference.
The as attribute
If a component has the as attribute, it is evaluated as the element specified by this attribute.
<x-ul as="ul"><!-- Evaluate as <ul> -->
<x-li as="li"></x-li><!-- Evaluate as <li> -->
<x-li as="li"></x-li><!-- Evaluate as <li> -->
<x-li as="li"></x-li><!-- Evaluate as <li> -->
</x-ul>
This evaluation also applies to its attributes that are inherited from the component.
<!-- Evaluate as <img src="image.png" alt="image"> -->
<x-img src="image.png" alt="image">
Next steps
- Using Presets — Choose a framework-specific preset (e.g.,
markuplint:recommended-react) - Applying Rules — Customize rules for your project
- Configuration Properties — Full reference for
parser,specs, andpretenders