migrate4-5
SkillFiles & storageGuides you through migrating markuplint configuration from v4 to v5. Detects current versions, reviews the migration guide, interactively confirms breaking changes and new rules with the user, updates config files and tests. For Claude Code.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the migrate4-5 skill
What this skill tells your AI
The instructions your AI receives, as published by markuplint/markuplint in skills/migrations/v4-v5/SKILL.md and read by ahel’s review.
Guides you through migrating markuplint from stable v4 (last release v4.18.3) to v5. Treat v5 alpha/rc rule names as if they never existed.
Install: npx skills add markuplint/markuplint@migrations/v4-v5
When to Use
Use this skill when the user requests any of the following:
- "Upgrade markuplint to v5"
- "Migrate markuplint from v4 to v5"
- "Update markuplint version"
- "markuplint migration"
Steps
1. Detect Current Versions
- Detect the current versions of markuplint-related packages (
markuplint,@markuplint/*) frompackage.jsonand list them - Locate configuration files (
.markuplintrc,.markuplintrc.json,markuplint.config.js, etc.) - Confirm Node.js is v24.0.0 or later (v4 documented v18.18.0). Stop and have the user upgrade Node before changing packages.
2. Review the Migration Guide
Documentation base URL: check the target v5 version with npx markuplint --version. If it contains alpha, beta, or rc, use https://next.markuplint.dev; otherwise use https://markuplint.dev.
- Website guide (users):
{base}/docs/migration/v4-to-v5/
Required pages: {base}/docs/migration/v4-to-v5/ (index), {base}/docs/migration/v4-to-v5/rules/rule-names (renames and splits), {base}/docs/migration/v4-to-v5/aria, {base}/docs/migration/v4-to-v5/cli, {base}/docs/migration/v4-to-v5/config
Also fetch when the config uses the feature:
| Topic | Website path |
|---|---|
| Framework parsers | {base}/docs/migration/v4-to-v5/framework |
invalid-attr split | {base}/docs/migration/v4-to-v5/rules/invalid-attr |
required-element | {base}/docs/migration/v4-to-v5/rules/required-element |
deprecated-element | {base}/docs/migration/v4-to-v5/rules/deprecated-element |
| Table model | {base}/docs/migration/v4-to-v5/rules/table-row-column-alignment |
| Parse errors (opt-in) | {base}/docs/migration/v4-to-v5/rules/parse-error |
| textlint removal | {base}/docs/migration/v4-to-v5/rules/textlint |
Inspect node_modules presets and rule-aliases only to confirm what the installed v5 actually expands — do not invent names from memory.
Do not migrate using wai-aria-* intermediate names, no-unsupported-features, script-content, srcset-sizes-constraint, or input-button-non-empty-value. Those were not stable v4 rule names.
3. Confirm with the User (use AskUserQuestion extensively)
Always use AskUserQuestion at each decision. Never decide for the user. Batch up to 4 related questions.
Phase 1: Silent gaps and CI (must ask)
These do not produce a deprecation warning:
- Raw (non-preset)
permitted-contents→ addno-disallowed-ancestor,require-ancestor,no-duplicate-sibling-attrto keep v4 coverage? - Raw
no-refer-to-non-existent-id→ addno-broken-fragment-link? (markuplint:html-standardalone still lacks this sibling;a11y/recommendedinclude it.) - Raw
label-has-control→ addlabel-no-multiple-controls? (markuplint:a11yalone does not enable the sibling;html-standard/recommendeddo.) v5label-has-controlonly reports a label with no associated control. - Table-model rules
no-table-cell-overlap,no-table-span-overflow,no-empty-table-trackescalate warning → error. Keep errors, or set"severity": "warning"to mimic v4? - v4 CI treated warnings as failures? Add
--no-allow-warnings(v5 allows warnings by default). - ARIA default is 1.3. Keep 1.3, or set
ruleCommonSettings.ariaVersionto"1.2"? wai-aria: true/markuplint:a11ynow also run checks that were off or absent in v4wai-ariadefaults:no-aria-on-presentational-children,no-focusable-in-aria-hidden,no-default-aria-value,require-parent-role,tab-requires-tabpanel. Keep them, or disable individually?
Phase 2: Preset extras on markuplint:recommended
v4 recommended did not include these; v5 does. Confirm whether to keep or disable:
markuplint:compat:no-unsupported-browser-features,no-nonstandard-features(needs browserslist for the former;no-experimental-featuresstays opt-in)markuplint:code-styles:case-sensitive-attr-name,case-sensitive-tag-namemarkuplint:security:no-event-handler-attrmarkuplint:html-standardnow enablesno-unknown-attr/no-disallowed-attr/no-invalid-attr-value(v4html-standarddid not includeinvalid-attr) and dropsno-duplicate-dt/no-ineffective-attr
Phase 3: Other breaking changes that apply
Ask only if the config uses the feature:
--configno longer merges with auto-discovered configextends: array rule values replace; nestedoptionsare shallow-mergedrequired-element→require-element; ghost elements no longer satisfy requirements (ignoreOmittedElementsdefaulttrue)invalid-attr{ type: X }wrapper removed; route options per the guide- htmx:
@markuplint/htmx-parser→@markuplint/htmx-spec(dropparserentry) - Alpine: keep parser; spec
@markuplint/alpine-parser/spec→@markuplint/alpine-spec @markuplint/rule-textlintremoved- pretenders on standard HTML/SVG tags are ignored
:closest()→:is(… *)(removed in v6)- Non-fatal HTML parse errors: opt in via
severity.parseError(seerules/parse-error.md). Off by default.
Phase 4: New rules not in any preset
Present opt-in rules (attr-order, class-naming, … — list from rule-names "no preset" set). For attr-order, the user must supply the exact order array; true is not enough.
4. Update Dependency Versions
- Bump
markuplintand every@markuplint/*to the same v5 version - Uninstall
@markuplint/htmx-parser/@markuplint/rule-textlintif present - Install
@markuplint/htmx-spec/@markuplint/alpine-specwhen those frameworks are in use
5. Update Configuration Files
- Rewrite deprecated rule names from Markuplint's deprecation warnings after one run (old names work until v6; still rewrite now)
- Apply the silent-gap siblings the user confirmed
- Set
ruleCommonSettings.ariaVersionif they chose 1.2 - Disable extra ARIA/preset rules they declined
- Convert
invalid-attr/required-element/ frameworkparser/specsas agreed - Named preset groups (
a11y/html-lang,a11y/wai-aria/*, …) can be toggled inruleswithout renaming the user's own nodeRules unless they want names
6. Triage newly flagged markup
Everything above is about config. Separately, v5 tightened a number of built-in checks so that markup which passed under v4 now fails with no config change involved — see Reference: newly flagged markup. Run markuplint once (after step 5) and treat any violation from that list as an expected v5 finding, not a false positive:
- Fix the markup, or
- If a specific case is load-bearing for this project, add a targeted
nodeRulesdisable/severity override and note why
Do not silence these by disabling the rule outright — they are spec-conformance checks, not new opt-in preferences.
7. Update Tests
- Run markuplint; include
ruleIdand Named Rule Groupnamein assertions when present --config/-cin tests loads only that file- Attribute-order and column numbers may shift if
attr-orderis adopted
8. Commit
Split by change type in the user's repo (example):
feat!: upgrade markuplint to v5— package.json + lockfilefix: migrate markuplint config for v5— configtest: update fixtures for markuplint v5
Reference: must-check (no warning)
| Situation | Add or change |
|---|---|
Raw permitted-contents | no-disallowed-ancestor, require-ancestor, no-duplicate-sibling-attr |
Raw no-refer-to-non-existent-id | no-broken-fragment-link |
Raw label-has-control | label-no-multiple-controls |
| Table model | three rules now error |
| CI on warnings | --no-allow-warnings |
Old renamed/split names still work with a deprecation warning until v6. Option-routed splits (stable v4): doctype, landmark-roles, required-h1, invalid-attr — do not blindly enable every sibling.
Reference: newly flagged markup (no config change)
Full detail with cited spec sections and examples: invalid-attr migration page (see also its "Additional patterns" section) and the markup-level-checks note near the top of the index page (fetch via {base} for the prerelease-vs-stable host, per step 2). Summary, grouped by enforcing rule:
| Rule | What now fails |
|---|---|
no-invalid-attr-value, no-disallowed-attr, require-attr (the invalid-attr split) | URL Living Standard strictness on every URL-typed attribute; empty URLs on src/action/poster/etc.; lang/hreflang validated against the IANA subtag registry; deprecated media= types/features and malformed media conditions; script attribute applicability (e.g. defer on type=module); meta[charset] must be literal utf-8; CSP3 grammar on meta[http-equiv=content-security-policy]; bdo[dir] excludes auto and is required; usemap="#"; itemid/itemtype require itemscope; input min/max per-type format; source[sizes] requires srcset |
no-prohibited-naming | Autonomous custom elements (<x-y>, no is=) without an explicit role can't carry aria-label/aria-labelledby/aria-braillelabel |
element-supports-aria-prop | Elements with properties: false (e.g. input[type=hidden]) reject all aria-*; aria-expanded disallowed on button[popovertarget] and summary in details |
permitted-contents | MathML elements enforce exact child counts (e.g. mfrac needs exactly two); nested SVG <a> rejected; <div> in <dl> allows only one dt+/dd+ group |
Also: nodeRules selectors now match HTML attribute names case-insensitively ([charset] matches <meta CHARSET>) — not a new violation, but can change which elements the user's own nodeRules target. See Config.
wai-aria expands to 21 rules; toggles are not mapped. See the ARIA guide for the v4 option table.
Reference: Named Rule Groups
Preset entries with a name (for example a11y/html-lang) can be disabled or given a different severity from rules:
rules: {
'a11y/html-lang': false,
'a11y/*': false,
}
Adding name to the user's own nodeRules is optional, not required for v4→v5.
Reference: -c / --config
v5 loads only the file passed to --config. It does not merge .markuplintrc. Tests that used v4 merge behavior must extends the project config or pass a complete file.
Reference: browserslist rules
no-unsupported-browser-features (in markuplint:compat, hence recommended) is a no-op without browserslist. no-experimental-features is not in the compat preset (opt-in). no-nonstandard-features is in compat.
Signals
- GitHub stars
- 614
- Forks
- 63
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
migrate4-5- Source
- github.com/markuplint/markuplint