Migrate to package:web

Dart's package:web exposes access to browser APIs, enabling interop between Dart applications and the web. Use package:web to interact with the browser and manipulate objects and elements in the DOM.

import 'package:web/web.dart';

void main() {
  final div = document.querySelector('div')!;
  div.text = 'Text set at ${}';

package:web vs dart:html


The goal of package:web is to revamp how Dart exposes web APIs by addressing several concerns with the existing Dart web libraries:

  1. Wasm compatibility

    Packages can only be compatible with Wasm if they use dart:js_interop and dart:js_interop_unsafe. package:web is based on dart:js_interop, so by default, it's supported on dart2wasm.

    Dart core web libraries, like dart:html and dart:svg, are not supported when compiling to Wasm.

  2. Staying modern

    package:web uses the Web IDL to automatically generate interop members and interop types for each declaration in the IDL. Generating references directly, as opposed to the additional members and abstractions in dart:html, allows package:web to be more concise, easier to understand, more consistent, and more able to stay up-to-date with the future of Web developments.

  3. Versioning

    Because it's a package, package:web can be versioned more easily than a library like dart:html and avoid breaking user code as it evolves. It also makes the code less exclusive and more open to contributions. Developers can create alternative interop declarations of their own and use them together with package:web without conflict.

These improvements naturally result in some implementation differences between package:web and dart:html. The changes that affect existing packages the most, like IDL renames and type tests, are addressed in the migration sections that follow. While we only refer to dart:html for brevity, the same migration patterns apply to any other Dart core web library like dart:svg.

Migrating from dart:html


Remove the dart:html import and replace it with package:web/web.dart:

import 'dart:html' as html; // Remove
import 'package:web/web.dart' as web; // Add

Add web to the dependencies in your pubspec:

  web: ^0.5.0

The following sections cover some of the common migration issues from dart:html to package:web.

For any other migration issues, check the dart-lang/web repo and file an issue.



Many of the symbols in dart:html were renamed from their original IDL declaration to align more with Dart style. For example, appendChild became append, HTMLElement became HtmlElement, etc.

In contrast, to reduce confusion, package:web uses the original names from the IDL definitions. A dart fix is available to convert types that have been renamed between dart:html and package:web.

After changing the import, any renamed objects will be new "undefined" errors. You can address these either:

  • From the CLI, by running dart fix --dry-run.
  • In your IDE, by selecting the dart fix: Rename to 'package:web name'.

The dart fix covers many of the common type renames. If you come across a dart:html type without a dart fix to rename it, first let us know by filing an issue.

Then, you can try manually discovering the package:web type name of an existing dart:html member by looking up its definition. The value of the @Native annotation on a dart:html member definition tells the compiler to treat any JS object of that type as the Dart class that it annotates. For example, the @Native annotation tells us that the native JS name of dart:html's HtmlElement member is HTMLElement, so the package:web name will also be HTMLElement:

class HtmlElement extends Element implements NoncedElement { }

To find the dart:html definition for an undefined member in package:web, try either of the following methods:

  • Ctrl or command click the undefined name in the IDE and choose Go to Definition.
  • Search for the name in the dart:html API docs and check its page under Annotations.

Similarly, you might find an undefined package:web API whose corresponding dart:html member's definition uses the keyword native. Check if the definition uses the @JSName annotation for a rename; the value of the annotation will tell you the name the member uses in package:web:

Node append(Node node) native;

native is an internal keyword that means the same as external in this context.

Type tests


It's common for code that uses dart:html to utilize runtime checks like is. When used with a dart:html object, is and as verify that the object is the JS type within the @Native annotation. In contrast, all package:web types are reified to JSObject. This means a runtime type test will result in different behavior between dart:html and package:web types.

To be able to perform type tests, migrate any dart:html code using is type tests to use interop methods like instanceOfString or the more convenient and typed isA helper (available from Dart 3.4 onward). The Compatibility, type checks, and casts section of the JS types page covers alternatives in detail.

obj is Window; // Remove
obj.instanceOfString('Window'); // Add

Type signatures


Many APIs in dart:html support various Dart types in their type signatures. Because dart:js_interop restricts the types that can be written, some of the members in package:web will now require you to convert the value before calling the member. Learn how to use interop conversion methods from the Conversions section of the JS types page.

window.addEventListener('click', callback); // Remove
window.addEventListener('click', callback.toJS); // Add

Generally, you can spot which methods need a conversion because they'll be flagged with some variation of the exception:

A value of type '...' can't be assigned to a variable of type 'JSFunction?'

Conditional imports


It is common for code to use a conditional import based on whether dart:html is supported to differentiate between native and web:

export 'src/hw_none.dart'
    if ( 'src/hw_io.dart'
    if (dart.library.html) 'src/hw_html.dart';

However, since dart:html is not supported when compiling to Wasm, the correct alternative now is to use dart.library.js_interop to differentiate between native and web:

export 'src/hw_none.dart'
    if ( 'src/hw_io.dart'
    if (dart.library.js_interop) 'src/hw_web.dart';

Virtual dispatch and mocking


dart:html classes supported virtual dispatch, but because JS interop uses extension types, virtual dispatch is not possible. Similarly, dynamic calls with package:web types won't work as expected (or, they might continue to work just by chance, but will stop when dart:html is removed), as their members are only available statically. Migrate all code that relies on virtual dispatch to avoid this issue.

One use case of virtual dispatch is mocking. If you have a mocking class that implements a dart:html class, it can't be used to implement a package:web type. Instead, prefer mocking the JS object itself. See the mocking tutorial for more information.

Non-native APIs


dart:html classes may also contain APIs that have a non-trivial implementation. These members may or may not exist in the package:web helpers. If your code relies on the specifics of that implementation, you may be able to copy the necessary code. However, if you think that's not tractable or if that code would be beneficial for other users as well, consider filing an issue or uploading a pull request to package:web to support that member.



In dart:html, callbacks are automatically zoned. This is not the case in package:web. There is no automatic binding of callbacks in the current zone.

If this matters for your application, you can still use zones, but you will have to write them yourself by binding the callback. See #54507 for more details. There is no conversion API or helper available yet to automatically do this.



The core of package:web contains external interop members, but does not provide other functionality that dart:html provided by default. To mitigate these differences, package:web contains helpers for additional support in handling a number of use cases that aren't directly available through the core interop. The helper library contains various members to expose some legacy features from the Dart web libraries.

For example, the core package:web only has support for adding and removing event listeners. Instead, you can use stream helpers that makes it easy to subscribe to events with Dart Streams without writing that code yourself.

// dart:html version
InputElement htmlInput = InputElement();
await htmlInput.onBlur.first;

// package:web version
HTMLInputElement webInput = document.createElement('input') as HTMLInputElement;
await webInput.onBlur.first;

You can find all the helpers and their documentation in the repo at package:web/helpers. They will continuously be updated to aid users in migration and make it easier to use the web APIs.



Here are some examples of packages that have been migrated from dart:html to package:web: