English
Quality
@templatical/quality is the umbrella package for Templatical's template-quality tooling — deterministic linters that catch authoring mistakes inside the editor and in headless / CI checks. MIT-licensed, ESM, no Vue, no DOM.
Linters
| Linter | What it catches | Default severities |
|---|---|---|
| Accessibility | Missing alt text, low contrast, vague CTAs, heading-skip, undersized touch targets, ALL CAPS body, target=_blank missing rel, missing preheader, … | mostly error/warning |
| Structure | Duplicate block IDs, sections with the wrong column count, nested sections, empty sections, empty columns, heroes that render as regular sections, background images Outlook on Windows can't layer | mostly error; some warning |
| Links | Dangerous URL schemes (javascript:), unsupported protocols, malformed mailto: / tel:, relative URLs, staging / localhost URLs leaking into a template | mostly warning; link.javascript-protocol is error |
| Deliverability | Mismatched URL-shaped link text, shortener links, IP-address links, and image-led emails with little text | warning or info |
| Compatibility | Client-breaking pass-through HTML and CSS, plus compiled Gmail size | warning or info |
| Localization | Missing translations, stale source hashes and differing merge tags | warning |
All linters return the same LintIssue shape and share the same options surface (LintOptions) — so consumers can run them in any combination, merge results, and filter by ruleId prefix (a11y.*, structure.*, link.*, deliverability.*, compat.*, localization.*) when grouping. Each linter's per-rule severities and tool-specific knobs live under its own namespace (accessibility, structure, links, deliverability, compatibility, localization); set any of them to false to disable that linter entirely.
Run everything in one call. lintTemplate(content, options?) is the single aggregate entry point — it runs all linters and returns the merged LintIssue[] (accessibility, then structure, then links, then deliverability, then compatibility, then localization). Reach for it by default; the per-linter functions stay exported for when you want to run just a subset. To skip a category, set its key to false: lintTemplate(content, { structure: false }). See Headless usage.
contentLocale selects the version to lint; locale selects messages. Dictionaries remain a union across languages. The three localization.* rules are listed in Options, with their fallback, staleness and merge-tag behavior.
Architecture
The package has no opinion on UI. The editor's useTemplateLint composable lazy-imports @templatical/quality, runs all linters via the single lintTemplate call on debounced content changes, and feeds the merged issues stream into the Issues sidebar tab and the per-block canvas badges. applyFix(issue) runs each patch through the editor's existing block-update path so fixes land as proper undo entries.
Install
@templatical/quality is an optional dependency of the editor and installs by default. Leaving it out is supported: the editor works without it and hides the Issues tab. For headless use or to add it back, install it explicitly:
bash
npm install @templatical/qualitybash
pnpm add @templatical/qualitybash
yarn add @templatical/qualitybash
bun add @templatical/qualityCDN users
If you load Templatical via CDN, there's nothing to install. The editor's CDN bundle ships @templatical/quality as a separate code-split chunk that lazy-loads automatically when linting is enabled.
Wire into the editor
Pass lint to init() or initCloud():
ts
import { init } from "@templatical/editor";
const editor = init({
container: "#editor",
locale: "en",
lint: {
accessibility: {
rules: {
"a11y.img-missing-alt": "warning", // soften from default 'error'
"a11y.text-all-caps": "off", // turn off entirely
},
thresholds: { minFontSize: 16 },
},
structure: {
rules: { "structure.empty-column": "info" }, // demote to info
},
links: {
rules: { "link.localhost-or-staging": "error" }, // promote before send
nonProductionHosts: ["*.staging.*", "*.preview.*"],
},
deliverability: { thresholds: { minTextLength: 250 } },
compatibility: { thresholds: { maxHtmlBytes: 92_160 } },
},
});The Issues tab and inline canvas badges appear automatically once the quality package is available. When lint.disabled === true — or when every per-tool key (accessibility, structure, links, deliverability, compatibility) is false — the editor never lazy-loads the package: no chunk download, no UI surface.
Set any single linter to false to drop just that tool's rules:
ts
init({ container: "#editor", lint: { links: false } });Quick links
- Options —
disabled,locale, per-toolaccessibility/structure/links/deliverability/compatibilitynamespaces. - Severity & fixes — severity model + how auto-fix patches land in the editor.
- Headless usage —
lintTemplate+ validating stored templates in CI / server save handlers. - Contributing locales — adding rule messages + vague-text dictionaries.
- Accessibility linter — what it catches, rule catalog.
- Structure linter — what it catches, rule catalog.
- Links linter — what it catches, rule catalog.
- Deliverability linter — content cues, rule catalog.
- Compatibility linter — pass-through client support and compiled size.