Dartvel, home
Docs
Features
Studio
Cloud
Compared

Search the site

GitHub
pub.dev

GETTING STARTED

Getting started
Existing Flutter apps
Existing Native apps
Run on your phone

APP

UI and styling
Routing
State
Accessibility
Keyboard shortcuts
Localization
Devices and desktop
Native device access
Media, 3D and XR

DATA

Data models
Forms
Search
Sync and offline
Import and export
Change capture
Database
Cache
File storage
Images
Privacy and erasure

BACKEND

Backend functions
Auth and sessions
Authorization
Queues and jobs
Workers and memory
Notifications and mail
Outbound HTTP
AI
Webhooks
GraphQL and OpenAPI
API keys and OAuth
Multi-tenancy
Billing and commerce
Modules

OPERATIONS

Edge security
Secrets and environments
Monitoring
Releases

SHIPPING

Build targets
Telegram Mini Apps
Static web hosting
Servers and deploying

REFERENCE

Testing
CLI reference
Coding agents

APP

Accessibility

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

Name what a screen reader announces

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.

Reach a form and a button from the keyboard

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.

Fail the build on an accessibility regression

dartvel build web

Copy 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.

Waive a finding with a reason

# pubspec.yaml
dartvel:
  accessibility:
    waivers:
      - route: /legacy-report
        rule: heading-order
        reason: Imported markup, rewrite tracked in #412

Copy 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.

Drive a page with switches or a remote

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

Select text beside a sidebar

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.

Check contrast and tap targets in code

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.

Status

Built

Spec section: Accessibility

PREVIOUS State Signals, derived signals and globals
NEXT Keyboard shortcuts Page and app shortcuts, safe typing and a shortcut sheet
GitHub
pub.dev
npm
Acknowledgements
Privacy
Terms

FSL-1.1-MIT licensed. Built with Dartvel.

Dartvel is made by

SigmaDev Digital

To the bottom