Stylesheet API
The stylesheet system lets html-to-document keep style rules outside of parsed DocumentElement.styles and resolve them later during serialization or adapter conversion.
This is especially useful for:
- HTML tag defaults that should behave like rules, not inline styles
- document-level rules passed into
init() - adapter-specific default styles
- adapters that need to inspect top-level at-rules such as
@page
The two style sources
There are now two different places styles can come from:
1. Inline element styles
These are styles parsed from the HTML itself, such as:
<p style="color: red; font-weight: bold">Hello</p>
Those values still end up on element.styles.
2. Stylesheet rules
These are rules stored in an IStylesheet instance.
They come from:
- built-in seeded rules like heading defaults
tags.defaultStylesstylesheetRulespassed toinit()- adapter
defaultStyles - a custom
stylesheetpassed toinit()
These rules are matched later by adapters using selectors.
Using stylesheetRules in init()
The main API for supplying rules directly is stylesheetRules.
import { init, DocxAdapter } from 'html-to-document';
const converter = init({
stylesheetRules: [
{
kind: 'style',
selectors: ['p.note', 'div.note'],
declarations: {
color: 'rebeccapurple',
fontWeight: 'bold',
},
},
{
kind: 'at-rule',
name: 'page',
descriptors: {
size: 'A4',
margin: '1in',
},
},
],
adapters: {
register: [{ format: 'docx', adapter: DocxAdapter }],
},
});
stylesheetRules is the easiest way to seed rule statements without manually creating a stylesheet instance first.
Using a custom stylesheet
If you need full control, you can provide your own stylesheet implementation or a prebuilt stylesheet instance.
import { init, createStylesheet } from 'html-to-document';
const stylesheet = createStylesheet();
stylesheet.addStyleRule('p.note', { color: 'green' });
stylesheet.addAtRule({
kind: 'at-rule',
name: 'page',
descriptors: { size: 'A4' },
});
const converter = init({
stylesheet,
});
When you pass stylesheet, the library still appends other seeded rules onto it.
What init() seeds into the stylesheet
When you call init(), the base stylesheet is built in this order:
- built-in base rules from
createBaseStylesheet() - rules generated from
tags.defaultStyles - rules from
stylesheetRules - for each adapter, format-specific
adapters.defaultStyles
Each adapter gets its own cloned stylesheet instance, so adapters do not share mutable rule state.
tags.defaultStyles now become stylesheet rules
tags.defaultStyles no longer gets merged into parsed element.styles.
Instead, each tag default is converted into a selector rule using the tag name itself.
const converter = init({
tags: {
defaultStyles: [
{ key: 'p', styles: { marginBottom: '8px' } },
{ key: 'table', styles: { borderStyle: 'solid' } },
],
},
});
That behaves like this conceptually:
[
{
kind: 'style',
selectors: ['p'],
declarations: { marginBottom: '8px' },
},
{
kind: 'style',
selectors: ['table'],
declarations: { borderStyle: 'solid' },
},
];
This means tag defaults remain selector-driven and can be resolved consistently by adapters.
adapters.defaultStyles also become stylesheet rules
Adapter default styles are still keyed by document element type:
const converter = init({
adapters: {
register: [{ format: 'docx', adapter: DocxAdapter }],
defaultStyles: [
{
format: 'docx',
styles: {
paragraph: { color: 'darkblue' },
heading: { fontFamily: 'Aptos Display' },
},
},
],
},
});
These are converted into rules internally and appended to that adapter's stylesheet.
Rule shapes
A stylesheet stores ordered StylesheetStatement values.
Style rule
const rule = {
kind: 'style',
selectors: ['p', '.note', '#intro'],
declarations: {
color: 'red',
textAlign: 'center',
},
} as const;
Style rules may also contain nested at-rules.
const rule = {
kind: 'style',
selectors: ['.card'],
declarations: { padding: '12px' },
children: [
{
kind: 'at-rule',
name: 'container',
prelude: 'card (min-width: 20rem)',
children: [
{
kind: 'style',
selectors: ['&'],
declarations: { padding: '16px' },
},
],
},
],
} as const;
These nested at-rules are currently preserved for consumers and future compatibility, but they are not evaluated by the current matcher.
At-rule
const rule = {
kind: 'at-rule',
name: 'page',
prelude: ':first',
descriptors: {
size: 'A4',
margin: '1in',
},
} as const;
Nested children are also supported for block at-rules. Style rules may also preserve nested at-rules for future use.
Selector support
The matcher currently supports simple selectors:
- tag selectors:
p - class selectors:
.note - id selectors:
#intro - universal selector:
* - attribute selectors:
[data-x][data-x="a"][data-x~="a"][lang|="en"][data-x^="pre"][data-x$="end"][data-x*="mid"]
- selector lists:
h1, h2, h3
Complex selector relationships like descendant, child, sibling, and pseudo selectors are not currently matched.
How adapters use the stylesheet
Adapters receive a stylesheet in IConverterDependencies.
class MyAdapter {
constructor(dependencies) {
console.log(dependencies.stylesheet);
}
}
Useful methods:
stylesheet.getMatchedStyles(element)
Returns only styles resolved from matching rules.
const matched = stylesheet.getMatchedStyles(element);
stylesheet.getComputedStyles(element, cascadedStyles)
Returns merged styles in this order:
{
...cascadedStyles,
...stylesheet.getMatchedStyles(element),
...(element.styles ?? {}),
}
So inline styles on the parsed element still win over stylesheet matches.
stylesheet.getStatements()
Returns all stored top-level statements.
stylesheet.getAtRules(name?)
Lets adapters inspect at-rules like @page.
const pageRules = stylesheet.getAtRules('page');
Built-in seeded rules
createBaseStylesheet() currently seeds heading defaults:
h1→fontSize: '32px',fontWeight: 'bold'h2→fontSize: '24px',fontWeight: 'bold'h3→fontSize: '18.72px',fontWeight: 'bold'h4→fontSize: '16px',fontWeight: 'bold'h5→fontSize: '13.28px',fontWeight: 'bold'h6→fontSize: '10.72px',fontWeight: 'bold'
There are many other rules which aim to make it look like the browsers' defaults.
Summary
- use
stylesheetRulesininit()to provide rules directly - use
stylesheetwhen you want to provide a custom stylesheet instance tags.defaultStylesnow seed stylesheet rules instead of inlining stylesadapters.defaultStylesare converted into adapter-specific stylesheet rules- inline HTML styles still live on
element.stylesand override stylesheet matches