OptiA11y.Rendering
0.4.0
dotnet add package OptiA11y.Rendering --version 0.4.0
NuGet\Install-Package OptiA11y.Rendering -Version 0.4.0
<PackageReference Include="OptiA11y.Rendering" Version="0.4.0" />
<PackageVersion Include="OptiA11y.Rendering" Version="0.4.0" />
<PackageReference Include="OptiA11y.Rendering" />
paket add OptiA11y.Rendering --version 0.4.0
#r "nuget: OptiA11y.Rendering, 0.4.0"
#:package OptiA11y.Rendering@0.4.0
#addin nuget:?package=OptiA11y.Rendering&version=0.4.0
#tool nuget:?package=OptiA11y.Rendering&version=0.4.0
OptiA11y.Rendering
Optional slice that captures actual rendered/computed behaviour of a page using headless
Chromium via Playwright: computed CSS for text (colors,
background, font size, font weight, text-align), the rendered bounding box and focus behaviour of
every interactive element, elements with automatic infinite animation, whether the page reflows
at a narrow viewport, and whether text clips under the WCAG 1.4.12 reference spacing overrides.
This complements — it does not replace — OptiA11y.Core's default HtmlFragmentParser, which can
only see inline style="" attributes and static markup, and therefore misses anything driven by
external stylesheets, CSS classes, theme styling, or actual browser layout/interaction.
Why a separate slice
OptiA11y.Core is intentionally CMS-agnostic and dependency-light (only HtmlAgilityPack). Adding
a full browser engine there would break that constraint for every host, even ones that never want
rendered-style checks. Keeping rendering in its own project means:
OptiA11y.CoreandOptiA11y.Cms's defaultAddOptiA11y()registration are completely unaffected — no new dependency, no behavior change.- Hosts that want real computed-style/layout checks opt in explicitly via
services.AddOptiA11yRenderedStyles(). - The rendered-vs-inline distinction is transparent: the rules that consume rendered-only
fragments (
ColorContrastRule,TextReadabilityRule,TargetSizeRule,FocusIndicatorRule,MotionRule,ReflowRule,TextSpacingRule, all in Core) don't know or care where their fragments came from — they just evaluate whatever fragment instances exist on theAuditDocument.
How it fits together
IRenderedStyleProvider.CaptureAsync(Uri)renders a page and returns aRenderedPageDiagnostics: aRenderedTextStyleper visible text node (computedcolor, effectivebackground-colorplus whether that background is an image/gradient,font-size,font-weight,text-align), aRenderedElementDiagnosticsper interactive element (rendered bounding box, whether focusing it changes its appearance at all), short descriptions of elements with an automatic infinite animation, whether the page overflows at a 320px-equivalent viewport, and text samples that clip under the WCAG 1.4.12 reference spacing overrides.PlaywrightRenderedStyleProvideris the only implementation, using headless Chromium. It fails soft (returnsRenderedPageDiagnostics.Empty) if the browser can't be launched or the page can't be reached, so a missing/misconfigured browser never breaks an audit. Internally it makes three passes over one page load: the default viewport (text styles, element diagnostics, animation scan), a resized 320px-wide viewport (reflow), and the default viewport again with the WCAG 1.4.12 stylesheet injected (text spacing).RenderedStyleFragmentBuilderconverts aRenderedPageDiagnosticsinto the sameColorContrastFragment/TextStyleFragment/TargetSizeFragment/FocusIndicatorFragment/MotionFragment/ReflowFragment/TextSpacingFragmentshapes Core's rules expect, reusing Core'sColorContrastCalculatorfor the WCAG contrast-ratio math.RunAuditHandler(inOptiA11y.Cms) appends these fragments to theAuditDocumentbefore running the rule engine, but only when both anIRenderedStyleProviderand a resolvableIContentPreviewUrlResolverURL are registered/available — otherwise this step is a no-op.
Enabling this slice in a host
services.AddOptiA11y(); // existing rule set, unaffected
services.AddOptiA11yRenderedStyles(); // opt-in: registers PlaywrightRenderedStyleProvider
// Required: hosts must supply their own resolver so a preview URL can be built per content item.
// The default NullContentPreviewUrlResolver returns null, which skips rendered-style enrichment entirely.
services.AddSingleton<IContentPreviewUrlResolver, MyContentPreviewUrlResolver>();
Prerequisite: install Playwright's browser binary
Playwright's browser binaries are not included in the NuGet package and must be installed once per machine/build agent:
pwsh bin/Debug/net10.0/playwright.ps1 install chromium
If this step is skipped, PlaywrightRenderedStyleProvider.CaptureAsync fails soft and returns
RenderedPageDiagnostics.Empty — audits continue to work using inline-style-only fragments from
HtmlFragmentParser, and the rendered-only rules simply find nothing to evaluate.
Known limitations
- Correlating rendered DOM text nodes back to a specific
SourceLocationis best-effort: fragments are matched by trimmed visible text content only, since Core's fragment model has no DOM/element reference. Pages with duplicate identical text may produce ambiguous correlation. - Requires a resolvable public/preview URL per content item. Building that URL is CMS/site-specific
and is intentionally left to the host via
IContentPreviewUrlResolver— no default implementation is provided beyond the no-op fallback. - Adds meaningful latency (a full browser page load, plus two extra passes over that page, per audited item), so this should typically be run on-demand rather than as part of every save, unlike the fast inline-style checks.
target-sizeexcludes inline text links and native checkbox/radio inputs (the exceptions WCAG 2.5.8 itself carves out), but cannot detect the SC's other exceptions (essential size, adjacent spacing) — a flagged element may still turn out to be a legitimate exception on manual review.motionandreflowcannot verify the "essential" and "2D-layout-required" exceptions their success criteria allow, which is why both reportNeedsReviewrather thanFail.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Playwright (>= 1.49.0)
- OptiA11y.Core (>= 0.4.0)
-
net8.0
- Microsoft.Playwright (>= 1.49.0)
- OptiA11y.Core (>= 0.4.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on OptiA11y.Rendering:
| Package | Downloads |
|---|---|
|
OptiA11y.Cms
Editorial accessibility assistance for Optimizely CMS. |
|
|
OptiA11y.Cms12
Editorial accessibility assistance for Optimizely CMS 12 (EPiServer.CMS.Core 12.x). Integrates the OptiA11y content audit panel into the CMS 12 edit UI. |
GitHub repositories
This package is not used by any popular GitHub repositories.