Skip to content

Quality

@templatical/quality is the umbrella package for Templatical's template-quality tooling — deterministic, JSON-only linters that catch authoring mistakes inside the editor and in headless / CI checks. MIT-licensed, ESM, no Vue, no DOM.

Linters

LinterWhat it catchesDefault severities
AccessibilityMissing alt text, low contrast, vague CTAs, heading-skip, undersized touch targets, ALL CAPS body, target=_blank missing rel, missing preheader, …mostly error/warning
StructureDuplicate block IDs, sections with the wrong column count, nested sections, empty sections, empty columnsmostly error; some warning

Both 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.*) when grouping.

Architecture

TemplateContentJSON block treefrom the editor or DBlintAccessibility()a11y.* ruleslintStructure()structure.* rulesLintIssue[]severity · message ·blockId · optional fixConsumed byIssues paneleditor sidebarCanvas badgesper-block iconsHeadless / CIstored templates

The package has no opinion on UI. The editor's useTemplateLint composable lazy-imports @templatical/quality, runs every exported linter on debounced content changes, and merges results into a single issues stream that drives 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

bash
npm install @templatical/quality
bash
pnpm add @templatical/quality
bash
yarn add @templatical/quality
bash
bun add @templatical/quality

The package is an optional peer of @templatical/editor. Install it to turn on the Issues sidebar tab and canvas badges. Skip it and the editor stays lean — the dynamic import is gated and tree-shakeable, so the linter chunk never downloads.

CDN 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: {
    rules: {
      "a11y.img-missing-alt": "warning",      // soften from default 'error'
      "a11y.text-all-caps": "off",            // turn off entirely
      "structure.empty-column": "info",       // demote to info
    },
    thresholds: { minFontSize: 16 },
  },
});

The Issues tab and inline canvas badges appear automatically once the optional peer is resolved. When lint.disabled === true, the editor never lazy-loads the package — no chunk download, no UI surface.