Writing custom element metadata: A simple component
HTML-Validate is fully data-driven and has no hardcoded rules for which elements exists and how they should work. This data is called element metadata and the validator bundles metadata for all HTML5 elements but makes no distinction between the bundled and user-provided metadata.
The bundled metadata is available in html5.json.
See also:
The component
Lets assume we have a custom element called <my-component>
.
If this element has no metadata anything goes for this element and the validator cannot help much.
Lets start off with some examples:
<!-- this is probably legal? -->
<div>
<my-component>lorem ipsum</my-component>
</div>
<!-- but should it work inside a span? -->
<span>
<my-component>lorem ipsum</my-component>
</span>
Depending on what the element consists of it might not be appropriate to use inside a <span>
but there is not yet any metadata available to tell if <my-component>
is allowed to be used in that context.
<!-- can it contain an interactive button? who knows? -->
<my-component>
<button type="button">click me!</button>
</my-component>
<!-- or is it allowed inside a button? -->
<button type="button">
<my-component>click me!</my-component>
</button>
Similarly there is not yet a way to tell if a button is allowed inside the component or if it may be used inside one.
Consider what would happen if <my-component>
wraps the content in an <a>
.
<!-- lets nest the component for fun and profit! -->
<my-component>
<my-component>
<my-component>
Sup dawg I heard you like components so I put components inside your components.
</my-component>
</my-component>
</my-component>
Most of the time it would make little sense to nest components but sometimes it isn't as obvious when it happens. Perhaps the nesting isn't direct but happens way down in the DOM tree.
All of above considered valid unless the metadata gives instructions how the element should be used.
The first step is creating a new file, e.g. elements.js
and configure the validator to read it.
elements.js
:
const { defineMetadata } = require("html-validate");
module.exports = defineMetadata({
"my-component": {},
});
Configure with:
{
"extends": ["html-validate:recommended"],
"elements": ["html5", "./elements.js"]
}
const { defineConfig } = require("html-validate");
module.exports = defineConfig({
extends: ["html-validate:recommended"],
elements: ["html5", "./elements.js"],
});
Rerunning the validation will now have the complete opposite effect, the element will not be allowed anywhere:
<div>
<my-component>lorem ipsum</my-component>
</div>
This happens because we have not yet told the validator what kind of element it is, we only provided it with empty metadata.
Most properties default to false
or []
.
Use no-unknown-elements
rule to find elements you have not yet provided metadata for.
Content categories
The first step to writing metadata is to decide which content category the element belongs to.
It can be one or many but most elements will probably only use flow
and/or phrasing
.
A bit simplified but flow elements can be thought as block-level <div>
and phrasing as inline <span>
(but all phrasing elements are also flow elements).
For instance, if our <my-component>
element were to work similar to a <div>
we can set the flow
property to true
.
const { defineMetadata } = require("html-validate");
module.exports = defineMetadata({
"my-component": {
+ flow: true,
},
});
The element will now be accepted inside another <div>
as flow elements can be nested inside each other.
<div>
<my-component>lorem ipsum</my-component>
</div>
It can not be nested inside a <span>
as a <span>
does not accept flow content (only other phrasing elements):
<span>
<my-component>lorem ipsum</my-component>
</span>
If we set the phrasing
property as well the element will be allowed inside a <span>
too:
const { defineMetadata } = require("html-validate");
module.exports = defineMetadata({
"my-component": {
flow: true,
+ phrasing: true,
},
});
<span>
<my-component>lorem ipsum</my-component>
</span>
Some elements might want only one or the other and some want both. Most of the time a phrasing elements belongs to both the flow and phrasing categories but the opposite is not true.
- If the component is meant to be used where a
<span>
would go set bothflow
andphrasing
totrue
. - If the component is meant to be used where a
<div>
would go set onlyflow
totrue
. - If the component wraps a
<a>
or<button>
element setinteractive
totrue
.
There are other content categories as well, check the element metadata reference and the official HTML5 specification for details of each one.
Case study: <div>
const { defineMetadata } = require("html-validate");
module.exports = defineMetadata({
div: {
flow: true,
permittedContent: ["@flow"],
},
});
As a generic flow content container the <div>
element simply sets the flow
property and uses the permittedContent
property to restrict its content to only allow other flow content (in practice this means almost all other elements).
In the next part, restricting element content, you will learn how to restrict both content and context of your components.