Story time for ages 3–5
Saturdays at 10:30 in the children’s room. Details
Saturdays at 10:30 in the children’s room. Details
---
>
```
The other operations are `RemoveAttribute`, `ReplaceText`, `InsertBefore`, `InsertAfter`, `Prepend`, `Append`, `Wrap`, `Unwrap`, `Replace`, `Remove`, `SetStyle` and `RemoveStyle`. [Edit HTML without reformatting it](https://recursiveai.org/onehtml/cookbook/edit-html-csharp-lossless/) shows several at once.
## Compute the accessibility tree
The accessibility tree is what a browser gives to screen readers: a role, an accessible name and states for each node that matters. `AccessibilityTree.Build` computes it from the document and its cascade (WAI-ARIA 1.2, HTML-AAM and AccName 1.2), using Chromium’s choices where the specifications leave room. No browser is involved.
```csharp
DocumentStyles styles = DocumentStyles.Create(doc);
AccessibilityTree tree = AccessibilityTree.Build(doc, styles);
string[] interesting = { "heading", "link", "button", "image", "textbox" };
foreach (AxNode node in tree.Nodes.Where(n => !n.IsIgnored && interesting.Contains(n.Role)))
{
Console.WriteLine($"{node.Role,-8} \"{node.Name.Value}\" from {node.Name.Source}");
}
```
```text
link "Harbor Street Library" from Contents
image "Harbor Street Library" from NativeAttribute
link "Books" from Contents
link "Events" from Contents
link "Visit" from Contents
heading "Upcoming events" from Contents
heading "Story time for ages 3–5" from Contents
link "Details" from Contents
image "" from None
heading "Repair café" from Contents
link "Partner site" from Contents
image "Volunteers at a workbench fixing a lamp" from NativeAttribute
textbox "Your email" from Placeholder
button "" from None
link "hello@example.org" from Contents
button "Back to top" from Contents
```
This tree was built from the original `doc`, so the story-time image still has no name. The submit button has none either: its only content is an `aria-hidden` icon. The logo link takes its name from the `alt` of the image inside it. The email field is named only by its placeholder.
## Check the page against WCAG 2.2 AA
`Checker` runs one rule catalogue that covers axe-core’s WCAG and best-practice rules and every W3C-approved ACT rule. Each rule is labeled with the WCAG criteria it checks. A profile picks the rules for a standard: `Wcag20AA`, `Wcag21AA`, `Wcag22AA`, or `All`, which adds best practices.
```csharp
var options = new CheckOptions { Profile = ValidationProfile.Wcag22AA };
CheckReport report = new Checker().Check(doc, options);
foreach (RuleResult failure in report.WithOutcome(Outcome.Failed))
{
Console.WriteLine($"{failure.RuleId} at {failure.Locator}: {failure.Message}");
}
Console.WriteLine($"Failed {report.Count(Outcome.Failed)}, needs review {report.Count(Outcome.NeedsReview)}, " +
$"needs render {report.Count(Outcome.NeedsRender)}, passed {report.Count(Outcome.Passed)}");
string sarif = report.ToSarifJson(new ExportOptions { Url = "events.html", Indented = true });
File.WriteAllText("events.sarif", sarif);
```
```text
button-name at form > button: The button has no accessible name.
image-alt at #content > article:nth-of-type(1) > img: The image has no accessible name.
Failed 2, needs review 18, needs render 10, passed 62
```
Every result has one of five outcomes:
- `Failed`, `Passed` and `Inapplicable` are decided.
- `NeedsReview` means markup and CSS can’t settle it, for example whether a heading describes its section. A person has to look.
- `NeedsRender` means the rule needs layout captured from a browser. Here all 10 are `target-size`, which needs the border boxes of each target and its neighbors. You can supply those as `RenderedFacts` (see [Rendered facts](https://recursiveai.org/onehtml/docs/rendered-facts/)), and `FactsMissing` on each result lists what is needed.
The checker doesn’t replace human review. It reports what it can’t decide instead of guessing.
`events.sarif` is SARIF 2.1.0. Each failure has its rule, its line and column, its byte offset and a source snippet. The rule’s entry in the file lists the WCAG criteria it checks.
```json
{
"ruleId": "button-name",
"ruleIndex": 28,
"kind": "fail",
"level": "error",
"rank": 90,
"message": {
"text": "The button has no accessible name."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "events.html",
"index": 0
},
"region": {
"startLine": 45,
"startColumn": 7,
"endLine": 45,
"endColumn": 29,
"byteOffset": 1584,
"byteLength": 22,
"snippet": {
"text": "