Recursive AI

Apply suggested accessibility fixes

All onehtml recipes

Take the edits the checker suggests for rules with exactly one correct fix, apply the ones you accept as lossless edits, and re-check only what changed.

Most accessibility failures need a person: only the author knows what an image shows or what a button does. A few have one fix that needs no judgment. For those, the checker attaches a suggested edit. In 1.0.0 these are aria-conditional-attr, aria-deprecated-role, aria-hidden-body, aria-valid-attr, avoid-inline-spacing, scope-attr-valid and tabindex. The library never applies a suggestion by itself.

The input is a staff page with six failures that have a suggested fix, and two listitem failures that don’t:

HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Staff directory | Harbor Street Library</title>
</head>
<body>
<main>
  <h1>Staff directory</h1>
  <ul role="directory">
    <li>Ana Ruiz, branch manager</li>
    <li>Sam Okafor, children's librarian</li>
  </ul>
  <table>
    <tr><th scope="column">Desk</th><th scope="column">Hours</th></tr>
    <tr><td>Reference</td><td>9 to 6</td></tr>
  </table>
  <p style="letter-spacing: 0.1em !important" aria-describedy="note">Call ahead for appointments.</p>
  <a href="/contact/" tabindex="3">Contact us</a>
</main>
</body>
</html>
C#
using System.Text;
using RecursiveAI.OneHtml;
using RecursiveAI.OneHtml.Css;
using RecursiveAI.OneHtml.Editing;
using RecursiveAI.OneHtml.Accessibility;
using RecursiveAI.OneHtml.Accessibility.Validation;
using RecursiveAI.OneHtml.Accessibility.Validation.Catalogue;
 
byte[] input = File.ReadAllBytes("staff.html");
Document doc = Document.Parse(input);
DocumentStyles styles = DocumentStyles.Create(doc);
AccessibilityTree tree = AccessibilityTree.Build(doc, styles);
 
var checker = new Checker();
var options = new CheckOptions { Profile = ValidationProfile.All };
CheckReport report = checker.Check(tree, options);
 
foreach (RuleResult result in report.WithOutcome(Outcome.Failed))
{
    string fix = result.SuggestedEdits.Count > 0 ? result.SuggestedEdits[0].Description : "no suggested edit";
    Console.WriteLine($"{result.RuleId}: {fix}");
}
 
// The library never applies a suggestion; you decide which ones to take
IReadOnlyList<Edit> suggestions = report.SuggestedEditList();
EditResult edit = doc.Apply(suggestions);
Console.WriteLine($"Applied {suggestions.Count} edits: {edit.Succeeded}");
 
// Re-check only what the edits reached
RestyleResult restyle = styles.Restyle(edit);
AccessibilityTree updated = tree.Update(edit, restyle);
CheckReport next = checker.Recheck(report, edit, restyle, updated);
Console.WriteLine($"Failed before: {report.Count(Outcome.Failed)}, after: {next.Count(Outcome.Failed)}");
Console.WriteLine($"Incremental: {next.Recheck!.Incremental}, rules carried over: {next.Recheck.RulesCarried}, " +
                  $"re-run in part: {next.Recheck.RulesRerunPartly}, in full: {next.Recheck.RulesRerunInFull}");
 
foreach (EditedRange range in edit.Changes!.Ranges)
{
    string before = Encoding.UTF8.GetString(input, range.OldByteRange.Start, range.OldByteRange.Length);
    Console.WriteLine($"[{before}] -> [{range.NewText}]");
}
File.WriteAllBytes("staff.fixed.html", edit.Document!.ToLosslessBytes());
Text
aria-deprecated-role: Use the role list instead of directory.
aria-valid-attr: Remove the aria-describedy attribute.
avoid-inline-spacing: Declare letter-spacing in the style attribute without !important.
listitem: no suggested edit
listitem: no suggested edit
scope-attr-valid: Remove the scope attribute.
scope-attr-valid: Remove the scope attribute.
tabindex: Set tabindex to 0; the focus order does not change.
Applied 6 edits: True
Failed before: 8, after: 0
Incremental: True, rules carried over: 7, re-run in part: 50, in full: 73
[directory] -> [list]
[ scope="column"] -> []
[ scope="column"] -> []
[ !important] -> []
[ aria-describedy="note"] -> []
[3] -> [0]

What happened:

  • Profile = ValidationProfile.All includes best-practice rules such as scope-attr-valid. With Wcag22AA alone, those wouldn’t run.
  • The two listitem failures have no suggestion, because the right fix depends on what the author meant. They went away anyway, because replacing the deprecated role="directory" with list put the items back inside a list.
  • SuggestedEditList() returns the suggestions as ordinary Edit objects without duplicates, so doc.Apply runs them with the usual lossless splicing and verification. Six one-range changes, and the rest of the file is byte-identical. To be selective, read result.SuggestedEdits per result and apply only the ones you accept.
  • Recheck gives the same report a full Check of the new tree would give, but it re-runs only what the edits reached. report.Recheck says how much was carried over.

To review suggestions before applying them, report.SuggestedEdits() returns them as one JSON edit envelope you can store, show in a review tool and replay later (see Save edits as JSON and replay them). Each edit’s meta carries the rule id, the criteria and "suggested": true. The rules and their fixes are documented in The checker.