Web Platform Design Principles

W3C Working Group Note,

This version:
https://www.w3.org/TR/2020/NOTE-design-principles-20201110/
Latest published version:
https://www.w3.org/TR/design-principles/
Editor's Draft:
https://w3ctag.github.io/design-principles/
Issue Tracking:
GitHub
Editor:
Sangwhan Moon (Invited Expert)
Former Editors:
Domenic Denicola (Google)
(Microsoft)
By:
Members of the TAG, past and present
Participate:
GitHub w3ctag/design-principles (file an issue; open issues)

Abstract

This document contains a set of design principles to be used when designing Web Platform technologies. These principles have been collected during the Technical Architecture Group’s discussions in reviewing developing specifications. We encourage specification designers to read this document and use it as a resource when making design decisions.

Status of this document

This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at https://www.w3.org/TR/.

This document was published by the W3C Technical Architecture Group (TAG) as a W3C Working Group Note.

Publication as a Working Group Note does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.

The TAG expects to update this document periodically.

The nomenclature "Working Group Note" for this document is intended only to convey the level of consensus around this document. The participants in the W3C Technical Architecture Group are elected by the W3C Membership to document and build consensus around principles of Web architecture, to interpret and clarify these principles when necessary, to resolve issues involving general Web architecture brought to the TAG, and to help coordinate cross-technology architecture developments inside and outside W3C.

Feedback and comments on this specification are welcome. Please file an issue in this document’s GitHub repository.

This document is governed by the 15 September 2020 W3C Process Document.

1. Principles behind design of Web APIs

1.1. Put user needs first (Priority of Constituencies)

If a trade-off needs to be made, always put user needs above all.

Similarly, when beginning to design an API, be sure to understand and document the user need that the API aims to address.

The internet is for end users: any change made to the web platform has the potential to affect vast numbers of people, and may have a profound impact on any person’s life. [RFC8890]

User needs come before the needs of web page authors, which come before than the needs of user agent implementors, which come before than the needs of specification writers, which come before theoretical purity.

Like all principles, this isn’t absolute. Ease of authoring affects how content reaches users. User agents have to prioritize finite engineering resources, which affects how features reach authors. Specification writers also have finite resources, and theoretical concerns reflect underlying needs of all of these groups.

See also:

1.2. It should be safe to visit a web page

When adding new features, design them to preserve the user expectation that visiting a web page is generally safe.

The Web is named for its hyperlinked structure. In order for the web to remain vibrant, users need to be able to expect that merely visiting any given link won’t have implications for the security of their computer, or for any essential aspects of their privacy.

For example, an API which allows any website to detect the use of assistive technologies may make users of these technologies feel unsafe visiting unknown web pages, since any web page may detect this private information.

If users have a realistic expectation of safety, they can make informed decisions between Web-based technologies and other technologies. For example, users may choose to use a web-based food ordering page, rather than installing an app, since installing a native app is riskier than visiting a web page.

To work towards making sure the reality of safety on the web matches users' expectations, we can take complementary approaches when adding new features:

A new feature which introduces safety risks may still improve user safety overall, if it allows users to perform a task more safely on a web page than it would be for them to install a native app to do the same thing. However, this benefit needs to be weighed against the common goal of users having a reasonable expectation of safety on web pages.

See also:

1.3. Trusted user interface should be trustworthy

Consider whether new features impact trusted user interfaces.

Users depend on trusted user interfaces such as the address bar, security indicators and permission prompts, to understand who they are interacting with and how. These trusted user interfaces must be able to be designed in a way that enables users to trust that the information they provide is genuine, and hasn’t been spoofed or hijacked by the website.

If a new feature allows untrusted user interfaces to resemble trusted user interfaces, this makes it more difficult for users to understand what information is trustworthy.

For example, JavaScript alert() allows a page to show a modal dialog which looks like part of the browser. This is often used to attempt to trick users into visiting scam websites. If this feature was proposed today, it would probably not proceed.

If a useful feature has the potential to cause harm to users, make sure that the user can give meaningful consent for that feature to be used, and that they can refuse consent effectively.

In order to give meaningful consent, the user must:

If a feature is powerful enough to require a user consent, but it’s impossible to explain to a typical user what they are consenting to, that’s a signal that you may need to reconsider the design of the feature.

If a permission prompt is shown, and the user doesn’t grant permission, the Web page should not be able to do anything that the user believes they have refused consent for.

By asking for consent, we can inform the user of what capabilities the web page does or doesn’t have, reinforcing their confidence that the web is safe. However, the user benefit of a new feature must justify the additional burden on users to decide whether to grant permission for each feature whenever it’s requested by a Web page.

For example, the Geolocation API grants access to a user’s location. This can help users in some contexts, like a mapping application, but may be dangerous to some users in other contexts - especially if used without the user’s knowledge. So that the user may decide whether their location may be used by a Web page, a permission prompt should be shown to the user asking whether to grant location access. If the user refuses permission, no location information is available to the Web page.

1.5. Support the full range of devices and platforms (Media Independence)

As much as possible, ensure that features on the web work across different input and output devices, screen sizes, interaction modes, platforms, and media.

One of the main values of the Web is that it’s extremely flexible: a Web page may be viewed on virtually any consumer computing device at a very wide range of screen sizes, may be used to generate printed media, and may be interacted with in a large number of different ways. New features should match the existing flexibility of the Web platform.

This doesn’t imply that features which don’t work in *every• possible context should be excluded. For example, hyperlinks can’t be visited when printed on paper, and the click event doesn’t translate perfectly to touch input devices where positioning and clicking the pointer occur in the same gesture (a "tap").

These features still work across a wide variety of contexts, and can be adapted to devices that don’t support their original intent - for example, a tap on a mobile device will fire a click event as a fallback.

Features should also be designed so that the easiest way to use them maintains flexibility.

The display: block, display: flex and display: grid layout models in CSS all default to placing content within the available space and without overlap, so that it works across screen sizes, and allows users to choose their own font and font size without causing text to overflow.

2. API Design Across Languages

2.1. Prefer simple solutions

Look hard for simple solutions to the user needs you intend to address.

Simple solutions are generally better than complex solutions, although they may be harder to find. Simpler features are easier for user agents to implement and test, more likely to be interoperable, and easier for authors to understand.

Make sure that your user needs are well-defined. This allows you to avoid scope creep, and make sure that your API does actually meet the needs of all users.

See also:

2.2. Name things thoughtfully

Name APIs with care. Naming APIs well makes it much easier for authors to use them correctly.

See the more detailed Naming principles section for specific guidance on naming.

2.3. New features should be detectable

Provide a way for authors to programmatically detect whether your feature is available, so that web content may gracefully handle the feature not being present.

An existing feature may not be available on a page for a number of reasons. Two of the more common reasons are because it hasn’t been implemented yet, or because it’s only available in secure contexts.

Authors shouldn’t need to write different code to handle each scenario. That way, even if an author only knows or cares about one scenario, the code will handle all of them.

When a feature is available but isn’t feasible to use because a required device isn’t present, it’s better to expose that the feature is available and have a separate way to detect that the device isn’t. This allows authors to handle a device not being available differently from the feature not being available, for example by suggesting the user connect or enable the device.

See § 8.2 Use care when exposing APIs for selecting or enumerating devices.

Authors should always be able to detect a feature from JavaScript, and in some cases the feature should also be detectable in the language where it’s used (such as @supports in CSS).

In some cases, it may not be appropriate to allow feature detection. Whether the feature should be detectable or not should be based on the user need for the feature: if there is a user need or design principle which would fail if feature detection were available for the feature, then you should not support feature detection.

Also, if a feature is generally not exposed to developers, it is not appropriate to support feature detection. For example, private browsing mode is a concept which is recognised in web specifications, but not exposed to authors. For private browsing mode to support the user’s needs, it must not be feature detected.

See also:

2.4. Consider limiting new features to secure contexts

Always limit your feature to secure contexts if it would pose a risk to the user without the authentication, integrity, or confidentiality that’s present only in secure contexts.

One example of a feature that should be limited to secure contexts is Geolocation, since it would be a risk to users' privacy to transmit their location in an insecure way.

For other features, TAG members past and present haven’t reached consensus on general advice. Some believe that all new features (other than features which are additions to existing features) should be limited to secure contexts. This would help encourage the use of HTTPS, helping users be more secure in general.

Others believe that features should only be limited to secure contexts if they have a known security or privacy impact. This lowers the barrier to entry for creating web pages that take advantage of new features which don’t impact user security or privacy.

Specification authors can limit most features defined in Web IDL, to secure contexts by using the [SecureContext] extended attribute on interfaces, namespaces, or their members (such as methods and attributes).

However, for some types of API (e.g., dispatching an event), limitation to secure contexts should just be defined in normative prose in the specification. If this is the case, consider whether there might be scope for adding a similar mechanism to [SecureContext] to make this process easier for future API developers.

As described in § 2.3 New features should be detectable, you should provide a way to programmatically detect whether a feature is available, including cases where the feature is unavailable because the context isn’t secure.

However, if, for some reason there is no way for code to gracefully handle the feature not being present, limiting the feature to secure contexts might cause problems for code (such as libraries) that may be used in either secure or non-secure contexts.

2.5. Don’t reveal that private browsing mode is engaged

Make sure that your feature doesn’t give authors a way to detect private browsing mode.

Some people use private browsing mode to protect their own personal safety. Because of this, the fact that someone is using private browsing mode may be sensitive information about them. This information may harm people if it is revealed to a web site controlled by others who have power over them (such as employers, parents, partners, or state actors).

Given such dangers, websites should not be able to detect that private browsing mode is engaged.

User Agents which support IndexedDB should not disable it in private browsing mode, because that would reveal that private browsing mode is engaged
The Payment Request API's show() method, when called, allows User Agents to act as if the user had immediately aborted the payment request.

This enables User Agents to automatically abort payment requests in private browsing mode (thus protecting sensitive information such as the user’s shipping or billing address) without revealing that private browsing mode is engaged.

See also:

2.6. Consider how your API should behave in private browsing mode

If necessary, specify how your API should behave differently in private browsing mode.

For example, if your API would reveal information that would allow someone to correlate a single user’s activity both in and out of private browsing mode, consider possible mitigations such as introducing noise, or using permission prompts to give the user extra information to help them meaningfully consent to this tracking (see § 1.4 Ask users for meaningful consent when appropriate).

Private browsing modes enable users to browse the web without leaving any trace of their private browsing on their device. Therefore, APIs which provide client-side storage should not persist data stored while private browsing mode is engaged after it’s disengaged. This can and should be done without revealing any detectable API differences to the site.

User Agents which support localStorage should not persist storage area changes made while private browsing mode is engaged.

If the User Agent has two simultaneous sessions with a site, one in private browsing mode and one not, storage area changes made in the private browsing mode session should not be revealed to the other browsing session, and vice versa. (The storage event should not be fired at the other session’s window object.)

See also:

2.7. Don’t reveal that assistive technologies are being used

Make sure that your API doesn’t provide a way for authors to detect that a user is using assistive technology without the user’s consent.

The web platform must be accessible to people with disabilities. If a site can detect that a user is using an assistive technology, that site can deny or restrict the user’s access to the services it provides.

People who make use of assistive technologies are often vulnerable members of society; their use of assistive technologies is sensitive information about them. If an API provides access to this information without the user’s consent, this sensitive information may be revealed to others (including state actors) who may wish them harm.

Sometimes people propose features which aim to improve the user experience for users of assistive technology, but which would reveal the user’s use of assistive technology as a side effect. While these are well intentioned, they violate § 1.2 It should be safe to visit a web page, so alternative solutions must be found.

The Accessibility Object Model (AOM) used to define a set of events which, when fired, revealed the use of assistive technology.

AOM has since removed these events and replaced them with synthetic DOM events which don’t reveal the use of assistive technology.

See also:

3. Cascading Style Sheets (CSS)

This section details design principles for features which are exposed via CSS.

3.1. Separate CSS properties based on what should cascade separately

Decide which values should be grouped together as CSS properties and which should be separate properties based on what makes sense to set independently.

CSS cascading allows declarations from different rules or different style sheets to override one another. If there is a logical set of attributes which should all be overridden together, they should be grouped together in a single property so that they cascade together. Likewise, if there are attributes which should be able to be overridden independently, they should be separate properties.

For example, the "size" and "sink" aspects of the initial-letter property belong in a single property because they are part of a single initial letter effect (e.g., a drop cap, sunken cap, or raised cap).

However, the initial-letter-align property should be separate because it sets an alignment policy for all of these effects across the document which is a general stylistic choice and a function of the script (e.g., Latin, Cyrillic, Arabic) used in the document.

3.2. Make appropriate choices for whether CSS properties are inherited

Decide whether a property should be inherited based on whether the effect of the property should be overridden or added to if set on an ancestor as well as a descendant.

If setting the property on a descendant element needs to override (rather than add to) the effect of setting it on an ancestor, then the property should probably be inherited. A specification of an non-inherited property requiring that the handling of an element look at the value of that property on its ancestors (which may also be slow) is a "code smell" that suggests that the property likely should have been inherited.

If setting the property on a descendant element is a separate effect that adds to setting it on an ancestor, then the property should probably not be inherited. A specification of an inherited property requiring that the handling of an element ignore the value of a property if it’s the same as the value on the parent element is a "code smell" that suggests that the property likely should not have been inherited.

If a property has an effect on text, then it’s almost always true that a descendant element needs to override (rather than add to) the effect of setting it on an ancestor, and the property should be inherited. This is also needed to maintain the design principle that inserting an unstyled inline element around a piece of text doesn’t change the appearance of that text.
For example, the background-image property is not inherited.

If the background-image property had been inherited, then the specification would have had to create a good bit of complexity to avoid a partially-transparent image being visibly repeated for each descendant element. This complexity probably would have involved behaving differently if the property had the same value on the parent element, which is the "code smell" mentioned above that suggests that a property likely should not have been inherited.

Another example is the font-size property, which is inherited. It sets the size of the font used for the text in the element, and continues to apply to any descendants that don’t have a declaration setting font-size to a different value.

If the font-size property were not inherited, then it would probably have to have an initial value that requires walking up the ancestor chain to find the nearest ancestor that doesn’t have that value. This is the "code smell" mentioned above that suggests that a property likely should have been inherited.

3.3. Choose the computed value type based on how the property should inherit

Choose the computed value of a CSS property based on how it will inherit, and the other properties which may depend on it, whether or not it has been inherited.

Note: the computed value should not be confused with the resolved value which is returned from the getComputedStyle() method.

Certain CSS properties are calculated with dependencies on others: for example, the computed value of a property specified in lh units depends on the line-height property which applies to the element being considered. The computed value for a property should be chosen so that the dependent values make sense, regardless of whether they are inherited.

For example, the line-height property may accept a <number> value, such as line-height: 1.4. This value represents a multiple of the font-size, so if the font-size is 20px, the actual value for the line height is 28px.

However, the computed value in this case is the <number> 1.4, not the <length> 28px.

The line-height property can be inherited into elements that have a different font-size, and any property on those elements which depends on line-height must take the relevant font-size into account, rather than the font-size for the element from which the line-height value was inherited.

<body style="font-size: 20px; line-height: 1.4">

  <p>This body text has a line height of 28px.</p>

  <h2 style="font-size: 200%">
    This heading has a line-height of 56px,
    not 28px, even though the line-height was declared on the body.
    This means that the 40px font won’t overflow the line height.
  </h2>
</body>

These number values are generally the preferred values to use for line-height because they inherit better than length values.

See also:

3.4. Naming of CSS properties and values

The names of CSS properties are usually nouns, and the names of their values are usually adjectives (although sometimes nouns).

Words in properties and values are separated by hyphens. Abbreviations are generally avoided.

Use the root form of words when possible rather than a form with a grammatical prefix or suffix (for example, "size" rather than "sizing").

The list of values of a property should generally be chosen so that new values can be added. Avoid values like yes, no, true, false, or things with more complex names that are basically equivalent to them.

Avoid words like "mode" or "state" in the names of properties, since properties are generally setting a mode or state.

See § 11 Naming principles for general (cross-language) advice on naming.

4. JavaScript Language

4.1. Web APIs are for JavaScript

When designing imperative APIs for the Web, use JavaScript. In particular, you can freely rely upon language-specific semantics and conventions, with no need to keep things generalized.

For example, the CustomElementRegistry.define() method takes a reference to a Constructor Method.

This takes advantage of the relatively recent addition of classes to JavaScript, and the fact that function references are very easy to use in JavaScript.

JavaScript is standardized under the name [ECMASCRIPT].
[WEBIDL] defines a separate "ECMAScript binding" section, but this doesn’t imply that Web IDL is intended to have bindings in other programming languages.

4.2. Preserve run-to-completion semantics

Don’t modify data accessed via JavaScript APIs while a JavaScript event loop is running.

A JavaScript Web API is generally a wrapper around a feature implemented in a lower-level language, such as C++ or Rust. Unlike those languages, when using JavaScript developers can expect that once a piece of code begins executing, it will continue executing until it has completed.

Because of that, JavaScript authors take for granted that the data available to a function won’t change unexpectedly while the function is running.

So if a JavaScript Web API exposes some piece of data, such as an object property, the user agent must not update that data while a JavaScript task is running. Instead, if the underlying data changes, queue a task to modify the exposed version of the data.

If a JavaScript task has accessed the navigator.onLine property, and browser’s online status changes, the property won’t be updated until the next task runs.

4.3. Don’t expose garbage collection

Ensure your JavaScript Web APIs don’t provide a way for an author to know the timing of garbage collection.

The timing of garbage collection is different in different user agents, and may change over time as user agents work on improving performance. If an API exposes the timing of garbage collection, it can cause programs to behave differently in different contexts. This means that authors need to write extra code to handle these differences. It may also make it more difficult for user agents to implement different garbage collection strategies, if there is enough code which depends on timing working a particular way.

This means that you shouldn’t expose any API that acts as a weak reference, e.g. with a property that becomes null once garbage collection runs. Object and data lifetimes in JavaScript code should be predictable.

getElementsByTagName returns an HTMLCollection object, which may be re-used if the method is called twice on the same Document object, with the same tag name. In practice, this means that the same object will be returned if and only if it has not been garbage collected. This means that the behaviour is different depending on the timing of garbage collection.

If getElementsByTagName were designed today, the advice to the designers would be to either reliably reuse the output, or to produce a new HTMLCollection each time it’s invoked.

getElementsByTagName gives no sign that it may depend on the timing of garbage collection. In contrast, APIs which are explicitly designed to depend on garbage collection, like WeakRef or FinalizationGroup, set accurate author expectations about the interaction with garbage collection.

5. JavaScript API Surface Concerns

5.1. Attributes should behave like data properties

[WEBIDL] attributes should act like simple JavaScript object properties.

In reality, IDL attributes are implemented as accessor properties with separate getter and setter functions. To make them act like JavaScript object properties:

If you were thinking about using an attribute, but it doesn’t behave this way, you should probably use a method instead.

For example, offsetTop performs layout, which can be complex and time-consuming. It would have been better if this had been a method like getBoundingClientRect().

5.2. Consider whether objects should be live or static

If an API gives access to an object representing some internal state, decide whether that object should continue to be updated as the state changes.

An object which represents the current state at all times is a live object, while an object which represents the state at the time it was created is a static object.

Live objects

If an object allows the author to change the internal state, that object should be live. For example, DOM Nodes are live objects, to allow the author to make changes to the document with an understanding of the current state.

Properties of live objects may be computed as they are accessed, instead of when the object is created. This makes live objects sometimes a better choice if the data needed is complex to compute, since there is no need to compute all the data before the object is returned.

A live object may also use less memory, since there is no need to copy data to a static version.

Static objects

If an object represents a list that might change, most often the object should be static. This is so that code iterating over the list doesn’t need to handle the possibility of the list changing in the middle.

getElementsByTagName returns a live object which represents a list, meaning that authors need to take care when iterating over its items:
let list = document.getElementsByTagName("td");

for (let i = 0; i < list.length; i++) {
    let td = list[i];
    let tr = document.createElement("tr");
    tr.innerHTML = td.outerHTML;

    // This has the side-effect of removing td from the list,
    // causing the iteration to become unpredictable.
    td.parentNode.replaceChild(tr, td);
}

The choice to have querySelectorAll() return static objects was made after spec authors noticed that getElementsByTagName was causing problems.

URLSearchParams isn’t static, even though it represents a list, because it’s the way for authors to change the query string of a URL.

Note: For maplike and setlike types, this advice may not apply, since these types were designed to behave well when they change while being iterated.

If it would not be possible to compute properties at the time they are accessed, a static object avoids having to keep the object updated until it’s garbage collected, even if it isn’t being used.

If a static object represents some state which may change frequently, it should be returned from a method, rather than available as an attribute.

See also:

5.3. Prefer dictionary parameters over primitive parameters

API methods should generally use dictionary parameters instead of a series of optional primitive parameters.

This makes the code that calls the method much more readable. It also makes the API more extensible in the future, particularly if multiple parameters with the same type are needed.

For example,
new Event("example",
          { bubbles: true,
            cancelable: false})

is much more readable than

new Event("example", true, false)

Also,

window.scrollBy({ left: 50, top: 0 })

is more readable than

window.scrollBy(50, 0)

The dictionary itself should be an optional parameter, so that if the author is happy with all of the default options, they can avoid passing an extra argument.

For example,
element.scrollIntoView(false, {});

is equivalent to

element.scrollIntoView(false);

See also:

5.4. Make function parameters optional if possible

If a parameter for an API function has a reasonable default value, make that parameter optional and specify the default value.

addEventListener() takes an optional boolean useCapture argument. Thie defaults to false, meaning that the event should be dispatched to the listener in the bubbling phase by default.

Note: Exceptions have been made for legacy interoperability reasons (such as XMLHttpRequest), but this should be considered a design mistake rather than recommended practice.

The API must be designed so that if a parameter is left out, the default value used is the same as converting undefined to the type of the parameter. For example, if a boolean parameter isn’t set, it must default to false.

See also:

5.5. Naming optional parameters

Name optional parameters to make the default behavior obvious without being named negatively.

This applies whether they are provided in a dictionary or as single parameters.

addEventListener() takes an options object which includes an option named once. This indicates that the listener should not be invoked repeatedly.

This option could have been named repeat, but that would require the default to be true. Instead of naming it noRepeat, the API authors named it once, to reflect the default behaviour without using a negative.

Other examples:

See also:

5.6. Classes should have constructors when possible

Make sure that any class that’s part of your API has a constructor, if appropriate.

By default, [WEBIDL] interfaces generate "non-constructible" classes: trying to create instances of them using new X() will throw a TypeError. To make them constructible, you can add appropriate constructor operation to your interface, and defining the algorithm for creating new instances of your class.

This allows JavaScript developers to create instances of the class for purposes such as testing, mocking, or interfacing with third-party libraries which accept instances of that class. It also gives authors the ability to create a subclass of the class, which is otherwise prevented, because of the way JavaScript handles subclasses.

This won’t be appropriate in all cases. For example:

The Event class, and all its derived interfaces, are constructible. This is useful when testing code which handles events: an author can construct an Event to pass to a method which handles that type of event.

The Window class isn’t constructible, because creating a new window is a privileged operation with significant side effects. Instead, the window.open() method is used to create new windows.

The ImageBitmap class isn’t constructible, as it represents an immutable, ready-to-paint bitmap image, and the process of getting it ready to paint must be done asynchronously. Instead, the createImageBitmap() factory method is used to create it.

The DOMTokenList class is, sadly, not constructible. This prevents the creation of custom elements that expose their token list attributes as DOMTokenLists.

Several non-constructible classes, like Navigator, History, or Crypto, are non-constructible because they are singletons representing access to per-window information. In these cases, something like the Web IDL namespace feature might have been a better fit, but these features were designed before namespaces, and go beyond what is currently possible with namespaces.

If your API requires this type of singleton, consider using a namespace, and File an issue on Web IDL if there is some problem with using them.

5.7. Use synchronous when appropriate

Where possible, prefer synchronous APIs when designing a new API. Synchronous APIs are simpler to use, and need less infrastructure set-up (such as making functions async).

An API should generally be synchronous if the following rules of thumb apply:

5.8. Design asynchronous APIs using Promises

If an API function needs to be asynchronous, use Promises, not callback functions.

Using Promises consistently across the Web platform means that APIs are easier to use together, such as by chaining promises. Promise-using code also tends to be easier to understand than code using callback functions.

An API might need to be asynchronous if:

See also:

5.9. Cancel asynchronous APIs/operations using AbortSignal

If an asynchronous function can be cancelled, allow authors to pass in an AbortSignal as part of an options dictionary.

const controller = new AbortController();
const signal = controller.signal;
geolocation.read({ signal });

Using AbortSignal consistently as the way to cancel an asychronous operation means that authors can write less complex code.

For example, there’s a pattern of using a single AbortSignal for several ongoing operations, and then using the corresponding AbortController to cancel all of the operations at once if necessary (such as if the user presses "cancel", or a single-page app navigation occurs.)

Even if cancellation can’t be guaranteed, you can still use an AbortController, because a call to abort() on AbortController is a request, rather than a guarantee.

5.10. Use strings for constants and enums

If your API needs a constant, or a set of enumerated values, use string values.

Strings are easier for developers to inspect, and in JavaScript engines there is no performance benefit from using integers instead of strings.

If you need to express a state which is a combination of properties, which might be expressed as a bitmask in another language, use a dictionary object instead. This object can be passed around as easily as a single bitmask value.

6. Event Design

6.1. Use promises for one time events

Follow the advice in the Writing Promise-Using Specifications guideline.

6.2. Events should fire before Promises resolve

If a Promise-based asynchronous algorithm dispatches events, it should dispatch them before the Promise resolves, rather than after.

This guarantees that once the Promise resolves, all effects of the algorithm have been applied. For example, if an author changes some state in reaction to an event which the Promise dispatches, they can be sure that all of the state is consistent if the Promise is resolved.

6.3. Don’t invent your own event listener-like infrastructure

When creating an API which allows authors to start and stop a process which generates notifications, use the existing event infrastructure to allow listening for the notifications. Create separate API controls to start/stop the underlying process.

For example, the Web Bluetooth API provides a startNotifications() method on the BluetoothRemoteGATTCharacteristic global object, which adds the object to the "active notification context set".

When the User Agent receives a notification from the Bluetooth device, it fires an event at the BluetoothRemoteGATTCharacteristic objects in the active notification context set.

See:

6.4. Always add event handler attributes

If your API adds a new event type, add a corresponding onyourevent event handler IDL attribute to the interface of any EventHandler which may handle the new event.

it’s important to continue to define event handler IDL attributes because:

For consistency, if the event needs to be handled by HTML and SVG elements, add the event handler IDL attributes on the GlobalEventHandlers interface mixin, instead of directly on the relevant element interface(s). Similarly, add event handler IDL attributes to WindowEventHandlers rather than Window.

6.5. Events are for notification

Events shouldn’t be used to trigger changes, only to deliver a notification that a change has already finished happening.

When a window is resized, an event named resize is fired at the Window object.

It’s not possible to stop the resize from happening by intercepting the event. Nor is it possible to fire a constructed resize event to cause the window to change size. The event can only notify the author that the resize has already happened.

6.6. Guard against potential recursion

If your API includes a long-running or complicated algorithm, prevent calling into the algorithm if it’s already running.

If an API method causes a long-running algorithm to begin, you should use events to notify user code of the progress of the algorithm. However, the user code which handles the event may call the same API method, causing the complex algorithm to run recursively. The same event may be fired again, causing the same event handler to be fired, and so on.

To prevent this, make sure that any "recursive" call into the API method simply returns immediately. This technique is "guarding" the algorithm.

AbortSignal's add, remove and signal abort each begin with a check of the signal’s aborted flag. If the flag is set, the rest of the algorithm doesn’t run.

In this case, a lot of the important complexity is in the algorithms run during the signal abort steps. These steps iterate through a collection of algorithms which are managed by the add and remove methods.

For example, the ReadableStreamPipeTo definition adds an algorithm into the AbortSignal's set of algorithms to be run when the signal abort steps are triggered, by calling abort() on the AbortController associated with the signal.

This algorithm is likely to resolve promises causing code to run, which may include attempting to call any of the methods on AbortSignal. Since signal abort involves iterating through the collection of algorithms, it should not be possible to modify that collection while it’s running.

And since signal abort would have triggered the code which caused the recursive call back in to signal abort, it’s important to avoid running these steps again if the signal is already in the process of the signal abort steps, to avoid recursion.

Note: A caution about early termination: if the algorithm being terminated would go on to ensure some critical state consistency, be sure to also make the relevant adjustments in state before early termination of the algorithm. Not doing so can lead to inconsistent state and end-user-visible bugs when implemented as-specified.

Note: Be cautious about throwing exceptions in early termination. Keep in mind the scenario in which developers will be invoking the algorithm, and whether they would reasonably expect to handle an exception in this [perhaps rare] case. For example, will this be the only exception in the algorithm?

You won’t always be able to "guard" in this way. For example, an algorithm may have too many entry-points to reliably check all of them. If that’s the case, another option is to defer calling the author code to a later task or microtask. This avoids a stack of recursion, but can’t avoid the risk of an endless loop of follow-up tasks.

Deferring an event is often specified as "queue a task to fire an event...".

You should always defer events if the algorithm that triggers the event could be running on a different thread or process. In this case, deferral ensures the events can be processed on the correct task in the task queue.

Both the "guarding" and the "deferring" approach have trade-offs.

"Guarding" an algorithm guarantees:

If the events are deferred instead:

Note: events that expose the possibility of recursion as described in this section were sometimes called "synchronous events". This terminology is discouraged as it implies that it’s possible to dispatch an event asynchronously. All events are dispatched synchronously. What is more often implied by "asynchronous event" is to defer firing an event.

6.7. State and Event subclasses

Where possible, use a plain Event with a specified type, and capture any state information in the target object.

It’s usually not necessary to create new subclasses of Event.

6.8. How to decide between Events and Observers

In general, use EventTarget and notification Events, rather than an Observer pattern, unless an EventTarget can’t work well for your feature.

Using an EventTarget ensures your feature benefits from improvements to the shared base class, such as the addition of the once.

If using events causes problems, such as unavoidable recursion, consider using an Observer pattern instead.

MutationObserver, Intersection Observer, Resize Observers, and IndexedDB Observers are all examples of an Observer pattern.

MutationObserver replaced the deprecated DOM Mutation Events after developers noticed that DOM Mutation Events

Mutation Observers:

Note: Events can also batch up notifications, but DOM Mutation Events were not designed to do this. Events don’t always need to participate in event propagation, but events on DOM Nodes usually do.

The Observer pattern works like this:

IntersectionObserver may be used like this:
function checkElementStillVisible(element, observer) {
    delete element.visibleTimeout;

    // Process any observations which may still be on the task queue
    processChanges(observer.takeRecords());

    if ('isVisible' in element) {
        delete element.isVisible;
        logAdImpressionToServer();

        // Stop observing this element
        observer.unobserve(element);
    }
}

function processChanges(changes) {
    changes.forEach(function(changeRecord) {
        var element = changeRecord.target;
        element.isVisible = isVisible(changeRecord.boundingClientRect,
                                      changeRecord.intersectionRect);
        if ('isVisible' in element) {
            // Element became visible
            element.visibleTimeout = setTimeout(() => {
                checkElementStillVisible(element, observer);
            }, 1000);
        } else {
            // Element became hidden
            if ('visibleTimeout' in element) {
                clearTimeout(element.visibleTimeout);
                delete element.visibleTimeout;
            }
        }
    });
}

// Create IntersectionObserver with callback and options
var observer = new IntersectionObserver(processChanges,
                                        { threshold: [0.5] });

// Begin observing "ad" element
var ad = document.querySelector('#ad');
observer.observe(ad);

(Example code adapted from the IntersectionObserver explainer.)

To use the Observer pattern, you need to define:

  1. the new Observer object type,

  2. an object type for observation options, and

  3. an object type for the records to be observed.

The trade-off for this extra work is the following advantages:

Observers and EventTargets have these things in common:

Here is an example of using a hypothetical version of IntersectionObserver that’s an EventTarget subclass:
const io = new ETIntersectionObserver(element, { root, rootMargin, threshold });

function listener(e) {
    for (const change of e.changes) {
        // ...
    }
}

io.addEventListener("intersect", listener);
io.removeEventListener("intersect", listener);

Compared to the Observer version:

In common with the Observer version:

These aspects can be achieved with either design.

See also:

7. Web IDL, Types, and Units

7.1. Use numeric types appropriately

If an API you’re designing uses numbers, use one of the following [WEBIDL] numeric types, unless there is a specific reason not to:

unrestricted double

Any JavaScript number, including infinities and NaN

double

Any JavaScript number, excluding infinities and NaN

[EnforceRange] long long

Any JavaScript number from -263 to 263, rounded to the nearest integer. If a number outside this range is given, the generated bindings will throw a TypeError.

[EnforceRange] unsigned long long

Any JavaScript number from 0 to 264, rounded to the nearest integer. If a number outside this range is given, the generated bindings will throw a TypeError.

JavaScript has only one numeric type, Number: IEEE 754 double-precision floating point, including ±0, ±Infinity, and NaN. [WEBIDL] numeric types represent rules for modifying any JavaScript number to belong to a subset with particular properties. These rules are run when a number is passed to the interface defined in IDL, whether a method or a property setter.

If you have extra rules which need to be applied to the number, you can specify those in your algorithm.

The WEBIDL rules for converting a JavaScript number to a number with fewer bits, such as an octet (8 bits, in the range [0, 255]), involves taking the modulo of the JavaScript number. For example, to convert a JavaScript number value of 300 to an octet, the bindings will first compute 300 modulo 255, so the resulting number will be 45, which might be surprising.

Instead, you can use [EnforceRange] octet to throw a TypeError for values outside of the octet range, or [Clamp] octet to clamp values to the octet range (for example, converting 300 to 255).

This also works for the other shorter types, such as short or long.

bigint should be used only when values greater than 253 or less than -253 are expected.

An API should not support both BigInt and Number simultaneously, either by supporting both types via polymorphism, or by adding separate, otherwise identical APIs which take BigInt and Number. This risks losing precision through implicit conversions, which defeats the purpose of BigInt.

7.2. Represent strings appropriately

When designing a web platform feature which operates on strings, use DOMString unless you have a specific reason not to.

Most string operations don’t need to interpret the code units inside of the string, so DOMString is the best choice. In the specific cases explained below, it might be appropriate to use either USVString or ByteString instead. [INFRA] [WEBIDL]

USVString is the Web IDL type that represents scalar value strings. For strings whose most common algorithms operate on scalar values (such as percent-encoding), or for operations which can’t handle surrogates in input (such as APIs that pass strings through to native platform APIs), USVString should be used.

Reflecting IDL attributes whose content attribute is defined to contain a URL (such as href) should use USVString. [HTML]

ByteString should only be used for representing data from protocols like HTTP which don’t distinguish between bytes and strings. It isn’t a general-purpose string type. If you need to represent a sequence of bytes, use Uint8Array.

7.3. Use milliseconds for time measurement

If you are designing an API that accepts a time measurement, express the time measurement in milliseconds.

Even if seconds (or some other time unit) are more natural in the domain of an API, sticking with milliseconds ensures that APIs are interoperable with one another. This means that authors don’t need to convert values used in one API to be used in another API, or keep track of which time unit is needed where.

This convention began with setTimeout and the Date API, and has been used since then.

Note: high-resolution time is usually represented as fractional milliseconds using a floating point value, not as an integer value of a smaller time unit like nanoseconds.

7.4. Use the appropriate type to represent times and dates

When representing date-times on the platform, use the DOMTimeStamp type. Use DOMHighResTimeStamp if authors must always be able to compare timestamps, regardless of the user’s time settings.

Don’t use the JavaScript Date class for representing specific date-time values. Date objects are mutable (may have their value changed), and there is no way to make them immutable.

For more background on why Date must not be used, see the following:

DOMTimeStamp values represent the number of milliseconds since 1970-01-01T00:00:00Z. They are derived from the system’s clock, which may be changed by the user, or automatically for things like time zone updates or daylight savings. This means that, in practice, a DOMTimeStamp created later in real time may have a lower value than a DOMTimeStamp created earlier. Also, if two DOMTimeStamps are created within the same millisecond, there’s no way to know which was created earlier.

So, if a date-time is only used to compare timing of events, and not to show a formatted date-time value to the user, consider using DOMHighResTimeStamp instead. See [HIGHRES-TIME] for more details.

7.5. Use Error or DOMException for errors

Represent errors in web APIs as ECMAScript error objects (e.g., Error) or as DOMException. whether they are exceptions, promise rejection values, or properties.

8. OS and Device Wrapper APIs

New APIs are now being developed in the web platform for interacting with devices. For example, authors wish to be able to use the web to connect with their microphones and cameras, generic sensors (such as gyroscope and accelerometer), Bluetooth and USB-connected peripherals, automobiles, etc.

These can be functionality provided by the underlying operating system, or provided by a native third-party library to interact with a device. These are an abstraction which "wrap" the native functionality without introducing significant complexity, while securing the API surface to the browser. So, these are called wrapper APIs.

This section contains principles for consideration when designing APIs for devices.

8.1. Use care when exposing identifying information about devices

If you need to give web sites access to information about a device, use the guidelines below to decide what information to expose.

Firstly, think carefully about whether it is really necessary to expose identifying information about the device at all. Consider whether your user needs could be satisfied by a less powerful API.

Exposing the presence of a device, additional information about a device, or device identifiers, each increase the risk of harming the user’s privacy.

One risk is that as more specific information is shared, the set of fingerprinting data available to sites gets larger. There are also other potential risks to user privacy.

Privacy Threat Model is not ready for prime time.

If there is no way to design a less powerful API, use these guidelines when exposing device information:

Limit information in the id

Include as little identifiable information as possible in device ids exposed to the web plaform. Identifiable information includes branding, make and model numbers, etc You can usually use a random number or a unique id instead. Make sure that your ids aren’t guessable, and aren’t re-used.

Keep the user in control

When the user chooses to clear browsing data, make sure any stored device ids are cleared.

Hide sensitive ids behind a user permission

If you can’t create a device id in an anonymous way, limit access to it. Make sure the user can provide meaningful consent to a Web page accessing this information.

Tie ids to the same-origin model

Create distinct unique ids for the same physical device for each origin that has has access to it.

If the same device is requested more than once by the same origin, return the same id for it (unless the user has cleared their browsing data). This allows authors to avoid having several copies of the same device.

Persistable when necessary

If a device id is time consuming to obtain, make sure authors can store an id generated in one session for use in a later session. You can do this by making sure that the procedure to generate the id consistently produces the same id for the same device, for each origin.

See also:

8.2. Use care when exposing APIs for selecting or enumerating devices

Look for ways to avoid enumerating devices. If you can’t avoid it, expose the least information possible.

If an API exposes the the existence, capabilities, or identifiers of more than one device, all of the risks in § 8.1 Use care when exposing identifying information about devices are multiplied by the number of devices. For the same reasons, consider whether your user needs could be satisfied by a less powerful API. [LEAST-POWER]

If the purpose of the API is to enable the user to select a device from the set of available devices of a particular kind, you may not need to expose a list to script at all. An API which invokes a User-Agent-provided device picker could suffice. Such an API:

When designing API which allows users to select a device, it may be necessary to also expose the fact that there are devices are available to be picked. This does expose one bit of fingerprinting data about the user’s environment to websites, so it isn’t quite as safe as an API which doesn’t have such a feature.

The RemotePlayback interface doesn’t expose a list of available remote playback devices. Instead, it allows the user to choose one device from a device picker provided by the User Agent.

It does enable websites to detect whether or not any remote playback device is available, so the website can show or hide a control the user can use to show the device picker.

The trade-off is that by allowing websites this extra bit of information, the API lets authors make their user interface less confusing. They can choose to show a button to trigger the picker only if at least one device is available.

If you must expose a list of devices, try to expose the smallest subset that satisfies your user needs.

For example, an API which allows the website to request a filtered or constrained list of devices is one option to keep the number of devices smaller. However, if authors are allowed to make multiple requests with different constraints, they may still be able to access the full list.

Finally, if you must expose the full list of devices of a particular kind, please rigorously define the order in which devices will be listed. This can reduce interoperability issues, and helps to mitigate fingerprinting. (Sort order could reveal other information: see Fingerprinting Guidance §5.2 Standardization for more.)

Note: While APIs should not expose a full list of devices in an implementation-defined order, they may need to for web compatibility reasons.

8.3. Native APIs don’t typically translate well to the web

When adapting native operating system APIs for the web, make sure the new web APIs are designed with web platform principles in mind.

Design based on functionality, not based on the existing API

You will probably not be able to directly translate an API available to native applications to be a web API.

Instead, consider the functionality available from the native API, and the user needs it addresses, and design an API which meets those user needs, even if the implmentation depends on the existing native API.

Be particularly careful about exposing the exact lifecycle and data structures of the underlying native APIs.

Make sure the web API can be implemented on more than one platform

When designing a wrapper API, consider how different platforms provide its functionality.

Ideally, all implementations should work exactly the same, but in some cases you may have a reason to expose options which only work on some platforms. If this happens, be sure to explain how authors should write code which works on all platforms. See § 2.3 New features should be detectable.

Underlying protocols should be open

APIs which require exchange with external hardware or services should not depend on closed or proprietary protocols. Depending on non-open protocols undermines the open nature of the web.

Design APIs to handle the user being off-line

If an API depends on some service which is provided by a remote server, make sure that the API functions well when the user can’t access the remote server for any reason.

Avoid additional fingerprinting surfaces

Wrapper APIs can unintentionally expose the user to a wider fingerprinting surface. Please read the TAG’s finding on unsanctioned tracking for additional details.

9. Other API Design Considerations

9.1. Polyfills

Polyfills can be hugely beneficial in helping to roll out new features to the web platform. The Technical Architecture Group finding on Polyfills and the Evolution of the Web offers guidance that should be considered in the development of new features, notably:

9.2. Extend existing manifest files rather than creating new ones

If your feature requires a manifest, investigate whether you can extend an existing manifest schema.

New web features should be self-contained and self-describing and ideally should not require an additional manifest file. Some of the existing manifest files include

We encourage people to extend existing manifest files. Always try to get the changes into the original spec, or at least discuss the extension with the spec editors. Having this discussion is more likely to result in a better design and lead to something that better integrates with the platform.

When designing new keys and values for a manifest, make sure they are needed (that is, they enable well-thought-out use-cases). Also, please check if a similar key exists. If an existing key/value pair does more or less what is needed, work with the existing spec to extend it to your use-case if possible.

There are certain times the original spec authors might not want to integrate changes to their manifest format immediately. This may be due to process (like going to CR), or due to the addition having a different scope, like extensions to Web App Manifest only affecting store or payment use-cases. In that case, it is acceptable to monkey-patch as long as that is agreed with the original spec editors.

when we write up a principle on monkey patching, be sure to take this nuance into account. <https://github.com/w3ctag/design-principles/issues/184>

An example of something that was done as a monkey patch that is scheduled to be integrated into the web app manifest in a future level (post-CR):

However, if your feature requires a complex set of metadata specific to a functional domain, the creation of a new manifest may be justified.

You may need to make a new manifest file if the domain of the manifest file is different from the existing manifest files. For example, if the fetch timing is different, or if the complexity of the manifest warrants it. Application metadata should be added to the Web App Manifest or be an extension of it. Manifests designated to be used for specific applications or which require interoperability with non-browsers may need to take a different approach. Payment Method Manifest, Publication Manifest, and Origin Policy are examples of these cases.

For example, if you have a single piece of metadata, even if the fetch timing is different than an existing manifest, it is probably best to use an existing manifest (or ideally design the feature in such a way that a manifest is not required). However, if your feature requires a complex set of metadata specific to a functional domain, the creation of a new manifest may be justified.

Note that in all cases, the naming conventions should be harmonized (see § 11 Naming principles).

Note: By principle, existing manifests use lowercase, underscore-delimited names. There have been times where it was useful to re-use dictionaries from a manifest in DOM APIs as well, which meant converting the names to camel-cased version. One such example is the image resource. For this reason, if a key can clearly be expressed as a single word, that is recommended.

10. Writing good specifications

This document mostly covers API design for the Web, but those who design APIs are hopefully also writing specifications for the APIs that they design.

10.1. Identify the audience of each requirement in your specification

Document both how authors should write good code using your API, and how implementers of your API should handle poorly-written code.

The web, especially in comparison to other platforms, is designed to be robust in accepting poorly-formed markup. This means that web pages which use older versions of web standards can still be viewed in newer user agents, and also that authors have a shallower learning curve.

To support this, web specification writers need to describe how to interpret poorly-formed markup, as well as well-formed markup.

Implementers need to be able to understand the "supported language", which is more complex than the "conforming language" which authors should be aiming to use.

For example, the Processing model for the <table> element explains how to process the contents of a <table> element, including cases where the contents do not conform to the Content model.

10.2. Specify completely and avoid ambiguity

When specifying how a feature should work, make sure that there is enough information so that authors don’t have to write different code to work with different implementations.

If a specification isn’t specific enough, implementers might make different choices which force authors to write extra code to handle the differences.

Implementers shouldn’t need to check details of other implementations to avoid this situation. Instead, the specification should be complete and clear enough on its own.

Note: This doesn’t mean that implementations can’t render things differently, or show different user interfaces for things like permission prompts.

Note: Implementers should file bugs against specifications which don’t give them clear enough information to write the implementation.

10.2.1. Defining algorithms in specifications

Write algorithms in a way that is clear and concise.

The most common way to write algorithms is to write an explicit sequence of steps. This often looks like pseudo-code.

The showModal() method is described as a numbered sequence of steps which clearly explains when to throw exceptions and when to run algorithms defined in other parts of the HTML spec.

When writing a sequence of steps, imagine that it is a piece of functional code.

Summarize the purpose of the algorithm before going into detail, so that readers can decide whether to read the steps or skip over them. For example take the following steps, which ensure that there is at most one pending X callback per top-level browsing context.

A plain sequence of steps is not always the best way to write an algorithm. For example, it might make sense to define or re-use a formal syntax or grammar to avoid repetition, or define specific states to be used in a state machine. When using extra constructs like these, the earlier advice still applies.

As much as possible, describe algorithms as closely as possible to how they would be implmented. This may make the spec harder to write, but it means that implementations don’t need to figure out how to translate what’s written in the specification to how it should actually be implemented. In particular, that may mean that different implementations make different decisions that may lead to later features being feasible in one implementation but not another.

CSS selectors are read and understood from left to right, but in practice are matched from right to left in implementations. This allows the most specific term to be matched or not matched quickly, avoiding unnecessary work. The CSS selector matching algorithm is written this way, instead of a hypothetical algorithm which would more closely match how CSS selectors are often read by CSS authors.

See also:

10.2.2. Use explicit flags for state

Instead of describing state with words, use explicit flags for state when writing algorithms.

Using explicit flags makes it clear whether or not the state changes in different error conditions, and makes it clear when the state described by the flags is reset.

11. Naming principles

Names take meaning from:

11.1. Use common words

API naming must be done in easily readable US English. Keep in mind that most web developers aren’t native English speakers. Whenever possible, names should be chosen that use common vocabulary a majority of English speakers are likely to understand when first encountering the name.

For example setSize is a more English-readable name than cardinality.

Value readability over brevity. Keep in mind, however, that sometimes the shorter name is the clearer one. For instance, it may be appropriate to use technical language or well-known terms of art in the specification where the API is defined.

For example, the Fetch API’s Body mixin’s json() method is named for the kind of object it returns. JSON is a well-known term of art among web developers likely to use the Fetch API. It would harm comprehension to name this API less directly connected to its return type. [FETCH]

11.2. Use ASCII names

Names must adhere to the local language restrictions, for example CSS ident rules etc. and should be in the ASCII range.

11.3. Consultation

Please consult widely on names in your APIs.

You may find good names or inspiration in surprising places.

Pay particular attention to advice you receive with clearly-stated rationale based on underlying principles.

Tantek Çelik extensively researched how to name the various pieces of a URL. The editors of the URL spec have relied on this research when editing that document. [URL]

Use Web consistent names

When choosing a name for feature or API that has exposure in other technology stacks, the preference should be towards the Web ecosystem naming convention rather than other communities.

The NFC standard uses the term media to refer to what the Web calls MIME type. In such cases, the naming of features or API for the purposes of Web NFC must prefer naming consistent with MIME type.

11.4. Future-proofing

Naming should be generic and future-proof whenever possible.

The name should not be directly associated with a brand or specific revision of the underlying technology whenever possible; technology becomes obsolete, and removing APIs from the web is difficult.

The Remote Playback API was not named after one of the pre-existing, proprietary systems it was inspired by (such as Chromecast or AirPlay). Instead, general terms that describe what the API does were chosen. [REMOTE-PLAYBACK]

The keydown and keyup KeyboardEvents were not named for the specific hardware bus that keyboards used at the time. Instead, generic names were chosen that are as applicable to today’s Bluetooth and USB keyboards as they were to PS/2 and ADB keyboards back then. [UIEVENTS]

11.5. Consistency

Naming schemes should aim for consistency, to avoid confusion.

Sets of related names should agree with each other in:

Boolean properties vs. boolean-returning methods

Boolean properties, options, or API parameters which are asking a question about their argument should not be prefixed with is, while methods that serve the same purpose, given that it has no side effects, should be prefixed with is to be consistent with the rest of the platform.

Use casing rules consistent with existing APIs

Although they haven’t always been uniformly followed, through the history of web platform API design, the following rules have emerged:

Casing rule Examples
Methods and properties
(Web IDL attributes, operations, and dictionary keys)
Camel case document.createAttribute()
document.compatMode
Classes and mixins
(Web IDL interfaces)
Pascal case NamedNodeMap
NonElementParentNode
Initialisms in APIs All caps, except when the first word in a method or property HTMLCollection
element.innerHTML
document.bgColor
Repeated initialisms in APIs Follow the same rule HTMLHRElement
RTCDTMFSender
The abbreviation of "identity" Id, except when the first word in a method or property node.getElementById()
event.pointerId
credential.id
Enumeration values Lowercase, dash-delimited "no-referrer-when-downgrade"
Events Lowercase, concatenated autocompleteerror
languagechange
HTML elements and attributes Lowercase, concatenated <figcaption>
<textarea maxlength>
JSON keys Lowercase, underscore-delimited manifest.short_name
Note that in particular, when a HTML attribute is reflected as a property, the attribute and property’s casings won’t necessarily match. For example, the HTML attribute ismap on img elements is reflected as the isMap property on HTMLImageElement.

The rules for JSON keys are meant to apply to specific JSON file formats sent over HTTP or stored on disk, and don’t apply to the general notion of JavaScript object keys.

Repeated initialisms are particularly non-uniform throughout the platform. Infamous historical examples that violate the above rules are XMLHttpRequest and HTMLHtmlElement. Don’t follow their example; instead always capitalize your initialisms, even if they are repeated.

11.6. Warning about dangerous features

Where possible, mark features that weaken the guarantees provided to developers by making their names start with "unsafe" so that this is more noticeable.

For example, Content Security Policy (CSP) provides protection against certain types of content injection vulnerabilities. CSP also provides features that weaken this guarantee, such as the unsafe-inline keyword, which reduces CSP’s own protections by allowing inline scripts.

11.7. Other resources

Some useful advice on how to write specifications is available elsewhere:

Acknowledgments

This document consists of principles which have been collected by TAG members past and present during TAG design reviews. We are indebted to everyone who has requested a design review from us.

The TAG would like to thank Adrian Hope-Bailie, Alan Stearns, Aleksandar Totic, Alex Russell, Andreas Stöckel, Andrew Betts, Anne van Kesteren, Benjamin C. Wiley Sittler, Boris Zbarsky, Brian Kardell, Charles McCathieNevile, Chris Wilson, Dan Connolly, Daniel Ehrenberg, Daniel Murphy, Domenic Denicola, Eiji Kitamura, Eric Shepherd, Ethan Resnick, fantasai, François Daoust, Henri Sivonen, HE Shi-Jun, Ian Hickson, Irene Knapp, Jake Archibald, Jeffrey Yasskin, Jeremy Roman, Jirka Kosek, Kevin Marks, Lachlan Hunt, Léonie Watson, L. Le Meur, Lukasz Olejnik, Maciej Stachowiak, Marcos Cáceres, Mark Nottingham, Martin Thomson, Matt Giuca, Matt Wolenetz, Michael[tm] Smith, Mike West, Nick Doty, Nigel Megitt, Nik Thierry, Ojan Vafai, Olli Pettay, Pete Snyder, Philip Jägenstedt, Philip Taylor, Reilly Grant, Richard Ishida, Rick Byers, Ryan Sleevi, Sergey Konstantinov, Stefan Zager, Stephen Stewart, Steven Faulkner, Surma, Tab Atkins-Bittner, Tantek Çelik, Tobie Langel, Travis Leithead, and Yoav Weiss for their contributions to this & the HTML Design Principles document which preceded it.

Special thanks to Anne van Kesteren and Maciej Stachowiak, who edited the HTML Design Principles document.

If you contributed to this document but your name is not listed above, please let the editors know so they can correct this omission.

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Conformant Algorithms

Requirements phrased in the imperative as part of algorithms (such as "strip any leading space characters" or "return false and abort these steps") are to be interpreted with the meaning of the key word ("must", "should", "may", etc) used in introducing the algorithm.

Conformance requirements phrased as algorithms or specific steps can be implemented in any manner, so long as the end result is equivalent. In particular, the algorithms defined in this specification are intended to be easy to understand and are not intended to be performant. Implementers are encouraged to optimize.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSS-CASCADE-5]
CSS Cascading and Inheritance Level 5 URL: https://drafts.csswg.org/css-cascade-5/
[CSS-CONDITIONAL-3]
CSS Conditional Rules Module Level 3 URL: https://drafts.csswg.org/css-conditional-3/
[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 4. URL: https://drafts.csswg.org/css-values-4/
[CSS2]
Bert Bos; et al. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification. URL: https://drafts.csswg.org/css2/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript Language Specification. URL: https://tc39.es/ecma262/
[HIGHRES-TIME]
Ilya Grigorik. High Resolution Time Level 2. URL: https://w3c.github.io/hr-time/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[IndexedDB-2]
Ali Alabbas; Joshua Bell. Indexed Database API 2.0. URL: https://w3c.github.io/IndexedDB/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[MEDIAQUERIES-5]
Dean Jackson; Florian Rivoal; Tab Atkins Jr.. Media Queries Level 5. URL: https://drafts.csswg.org/mediaqueries-5/
[PAYMENT-REQUEST]
Marcos Caceres; et al. Payment Request API. URL: https://w3c.github.io/payment-request/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/
[WEBIDL]
Boris Zbarsky. Web IDL. URL: https://heycam.github.io/webidl/
[XHR]
Anne van Kesteren. XMLHttpRequest Standard. Living Standard. URL: https://xhr.spec.whatwg.org/

Informative References

[CSS-BACKGROUNDS-3]
Bert Bos; Elika Etemad; Brad Kemper. CSS Backgrounds and Borders Module Level 3. URL: https://drafts.csswg.org/css-backgrounds/
[CSS-FONTS-3]
John Daggett; Myles Maxfield; Chris Lilley. CSS Fonts Module Level 3. URL: https://drafts.csswg.org/css-fonts-3/
[CSS-INLINE-3]
Dave Cramer; Elika Etemad; Steve Zilles. CSS Inline Layout Module Level 3. URL: https://drafts.csswg.org/css-inline-3/
[CSS-VALUES-3]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 3. URL: https://drafts.csswg.org/css-values-3/
[CSSOM-1]
Simon Pieters; Glenn Adams. CSS Object Model (CSSOM). URL: https://drafts.csswg.org/cssom/
[CSSOM-VIEW-1]
Simon Pieters. CSSOM View Module. URL: https://drafts.csswg.org/cssom-view/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[FINGERPRINTING-GUIDANCE]
Nick Doty. Mitigating Browser Fingerprinting in Web Specifications. 28 March 2019. NOTE. URL: https://www.w3.org/TR/fingerprinting-guidance/
[LEAST-POWER]
Tim Berners-Lee; Noah Mendelsohn. The Rule of Least Power. 23 February 2006. TAG Finding. URL: https://www.w3.org/2001/tag/doc/leastPower
[REMOTE-PLAYBACK]
Mounir Lamouri; Anton Vayvod. Remote Playback API. URL: https://w3c.github.io/remote-playback/
[RFC8890]
M. Nottingham. The Internet is for End Users. August 2020. Informational. URL: https://tools.ietf.org/html/rfc8890
[UIEVENTS]
Gary Kacmarcik; Travis Leithead; Doug Schepers. UI Events. URL: https://w3c.github.io/uievents/
[UNSANCTIONED-TRACKING]
Mark Nottingham. Unsanctioned Web Tracking. 17 July 2015. TAG Finding. URL: https://www.w3.org/2001/tag/doc/unsanctioned-tracking/

Issues Index

Privacy Threat Model is not ready for prime time.
when we write up a principle on monkey patching, be sure to take this nuance into account. <https://github.com/w3ctag/design-principles/issues/184>