Search the site
APP
dartvel build web fails when a screen reader would meet an unnamed button or a broken heading order.
Switch users and TV remotes can drive any page with nothing added.
Forms are driven from the keyboard with nothing added too: Tab walks them, Enter submits, and a refused save puts the focus on the field at fault.
ON THIS PAGE
Name what a screen reader announces
Reach a form and a button from the keyboard
Fail the build on an accessibility regression
Waive a finding with a reason
Drive a page with switches or a remote
Select text beside a sidebar
Check contrast and tap targets in code
Status
Widget checkoutButton(VoidCallback onCheckout) => DVText('Pay now').modifier(
DVModifier()
.paddingSymmetric(horizontal: 20, vertical: 14)
.minimumTapTarget()
.semanticButton()
.semanticLabel('Pay for the items in your cart')
.onTap(onCheckout),
);Copy code to clipboard
semanticButton, semanticLabel, semanticHint and semanticHeading sit on the same modifier chain as padding and colour.
minimumTapTarget() grows the box to a size a finger can hit.
context.screen.reducedMotion follows the system setting, and animate() already respects it.
Everything drawn with .onTap() or .onPressed() is a focusable control, whichever primitive it was reached for: Tab reaches it in the order it is drawn, Enter or Space presses it, and a focus ring is drawn while it holds the focus. The control draws its own ring, including in an application that never installed Material widgets.
A control names itself to a screen reader from the text it draws, or from .semanticLabel(). One node: a reader announces each control once.
The node says whether it holds the focus and offers the action that moves the focus onto it, so a reader who cannot press Tab can still get to it.
In a form, Tab walks the fields and then the controls. Enter moves to the next field and submits from the last one.
A refused save is announced in a live region, written under the field it names, and given the focus.
A password field starts with a Show password control, which can be turned off or replaced. See Forms.
dartvel build webCopy code to clipboard
After the web build, Dartvel loads each route in Chrome, reads the semantics tree a screen reader receives, and stops the build on any of these.
link-name
Fails when: A link has no accessible name
control-name
Fails when: A button or input has no accessible name
heading-name
Fails when: A heading is empty
page-heading
Fails when: A page has no headings, or no level 1
heading-order
Fails when: A level is skipped going down, such as h1 straight to h3
Chrome is needed
The audit reads a real browser's tree. On a machine with no Chrome the build still finishes, and the audit does not run.
# pubspec.yaml
dartvel:
accessibility:
waivers:
- route: /legacy-report
rule: heading-order
reason: Imported markup, rewrite tracked in #412Copy code to clipboard
A waiver with no reason, or naming a rule the audit lacks, stops the build.
rule: * waives every rule on that route.
A waiver that matches nothing is printed, so stale ones get noticed.
Nothing to add and nothing to wrap. Every page is driven by a TV remote, by one or two switches, and by a keyboard, the same way every page already scrolls from the arrow keys.
A remote's D-pad moves focus and its select key activates.
While switch control is on, Space steps focus and Enter activates. Until it is on, those keys belong to the page, so an ordinary keyboard user is never hijacked.
Turn it on with DV.Accessibility.switchControl.enabled, and choose the keys and an auto-scan interval in settings.
Kiosk mode never blocks these keys, so a locked kiosk stays usable.
In a text field the typing keys stay the field's: Space types, the arrows, Home and End move the caret, and Enter submits the form.
// Every page is already reachable by a remote, by switches and by a
// keyboard. This is the whole of what an application does about it: turn
// switch control on for the reader who needs it.
void turnSwitchControlOn() => DV.Accessibility.switchControl.enabled = true;
// And, where the reader's switch is not the usual key, say which it is.
// autoScan steps focus on a timer, which is what a single-switch user needs:
// one switch to select, and the stepping done for them.
void oneSwitch() {
DV.Accessibility.switchControl
..settings = const DVSwitchControlSettings(
next: LogicalKeyboardKey.f7,
select: LogicalKeyboardKey.f8,
)
..autoScan = const Duration(seconds: 2);
}Copy code to clipboard
Every page's text can be selected and copied. Beside a sidebar, rail or list pane of separate items, wrap the sidebar in DVSelectionColumn, so a drag across the content next to it selects what it crosses.
Text inside the column stays selectable.
A drag from the column into the content selects both, in order.
With no selection area above, it draws its child and nothing else.
final DVAccessibilityReport report = DV.Accessibility.report(<DVAccessibilityCheck>[
DV.Accessibility.contrast(
foreground: const Color(0xFF6B7280),
background: const Color(0xFFFFFFFF),
),
DV.Accessibility.tapTarget(size: const Size(40, 40)),
]);
for (final DVAccessibilityCheck failure in report.failures) {
DV.log(failure.message); // Tap target 40.0x40.0 is smaller than 48.0x48.0.
}Copy code to clipboard
contrast uses the WCAG ratio, 4.5 by default. tapTarget uses 48 by 48, the Material minimum.
Built
Spec section: Accessibility
FSL-1.1-MIT licensed. Built with Dartvel.
Dartvel is made by
To the bottom