Skip to content
Open UI

Openable API (Explainer)

Table of Contents

Note: The naming of various aspects of this API is intentionally bad, this is placeholder. The actual naming will be decided upon in the future.

Background

TODO

Goals

TODO

See Also

TODO

API Shape

This section lays out the full details of this proposal. If you’d prefer, you can skip to the examples section to see the code.

HTML Content Attribute

A new boolean content attribute, openable, controls the behavior.

So this markup represents openable content:

<div openable>I am an openable</div>

As written above, the <div> will be rendered display:none by the UA stylesheet, meaning it will not be shown when the page is loaded. To open the openable, one of several methods can be used: declarative triggering, JavaScript triggering.

Opening and Closing an Openable

There are several ways to “open” an openable, and they are discussed in this section. When any of these methods are used to open an openable, it will be made visible.

Page Load Trigger

As mentioned above, a <div openable> will be hidden by default. If it is desired that the openable should be shown automatically upon page load, the defaultopen attribute can be applied:

<div openable defaultopen></div>

In this case, the UA will immediately call openOpenable() on the element, as it is parsed.

The defaultopen content attribute can also be accessed via IDL:

myDiv.defaultOpen = true

Declarative Triggers

A common design pattern is to have a button which makes an openable visible. To facilitate this pattern, and avoid the need for JavaScript in this common case, this API integrates with command invokers.

<button type="button" command="toggle-openable" commandfor="foo">Toggle the openable</button>
<div id="foo" openable>Openable content</div>

When the button in this example is activated, the UA will call .toggleOpenable() on the <div id=foo openable> element. In this way, no JavaScript will be necessary for this use case.

If the desire is to have a button that only opens or only closes an openable, the following markup can be used:

<button type="button" command="toggle-openable" commandfor="foo">Toggle the openable</button>
<button type="button" command="open-openable" commandfor="foo">Open the openable</button>
<button type="button" command="close-openable" commandfor="foo">Close the openable</button>
<div id="foo" openable>Openable content</div>

When the command and commandfor attributes are applied to an activating element, the UA will automatically map this element with the appropriate accessibility semantics. For instance, the initial implementation will expose the appropriate aria-expanded state based on whether the openable is open or closed.

The declarative trigger attributes can also be accessed via IDL:

// Note that `commandFor` directly sets an element reference:
myButton.commandFor = myElement
myButton.command = 'show'

See the invokers explainer for more information on how these attributes work.

JavaScript Trigger

To open or close the Openable via JavaScript, there are three methods on HTMLElement:

const openable = document.querySelector('[openable]')
openable.openOpenable()
openable.closeOpenable()
openable.toggleOpenable()

Calling openOpenable() on an element that has an openable attribute will cause the UA to remove the display:none rule from the element. Calling closeOpenable() on an open openable will re-apply display:none.

There are conditions that will cause openOpenable(), closeOpenable(), and toggleOpenable() to throw an exception:

  1. Calling any of the three methods on an element that does not have the openable attribute. This will throw a NotSupportedError DOMException.

CSS Pseudo Class

When an openable is open, it will match the :open pseudo class:

const openable = document.createElement('div')
openable.openable = true;
openable.matches(':open') === false
openable.openOpenable()
openable.matches(':open') === true

Open vs Closed Openables

As explained in more detail below, the styling rules manage “open” vs. “closed” via these UA stylesheet rules:

[openable]{...}:not(:open) {
  display: none;
}

The above rules mean that an openable, when not “open”, has display:none applied, and that style is removed when one of the methods above is used to open the openable. Note that the display:none UA stylesheet rule is not !important. In other words, developer style rules can be used to override this UA style to make a not-open openable visible in the page. Care must be taken, if the display property is set by developer CSS, to ensure the openable visibility isn’t adversely affected. For example, developer CSS could do this:

/* Make the openable a flexbox but only when its open: */
[openable]:open {
  display: flex;
}

Be mindful when using other stylesheets that you haven’t authored. These may set the display value on your openable elements. For example, if you use a menu element as an openable and you use earlier versions of normalize.css. In this case, normalize.css has a rule that sets display: flex on menu elements.

Rendering

The UA stylesheet for openables will look like this:

/** Hide the contents of the openable when it's not open */
[openable]:not([hidden=until-found]):not(:open) {
  display: none;
}

/** Revert hidden=until-found's style when the openable is open */
[openable][hidden=until-found]:open {
  content-visibility: revert;
}

IDL Attribute and Feature Detection

The openable content attribute will be reflected as a boolean IDL attribute:

[Exposed=Window]
partial interface HTMLElement {
  attribute boolean openable;

  undefined openOpenable();
  undefined closeOpenable();
  boolean toggleOpenable(optional ToggleOpenableOptions options = {});
  boolean? defaultOpen;
}

dictionary ToggleOpenableOptions {
  boolean force;
}

This not only allows developer ease-of-use from JavaScript, but also allows for a feature detection mechanism:

function supportsOpenable() {
  return HTMLElement.prototype.hasOwnProperty('openable')
}

Events

An event is fired synchronously when an openable is open or close. This event can be used, for example, to populate content for the openable just in time before it is opened, or update server data when it closes. The event provides a currentState and newState for the openable. The value of these properties can either be “open” or “closed”. The events looks like this:

openable.addEventListener('beforetoggle', (beforeToggleEvent) => {
  if (beforeToggleEvent.currentState === 'closed') console.info('Openable is closed')
  if (beforeToggleEvent.newState === 'open') console.info('Openable is being shown')
})

The beforetoggle event is cancellable when the newState is equal to “open”. Doing so keeps the openable from being opened. You can’t cancel the closing of an openable. The beforetoggle event is fired synchronously.

Additionally an asynchronous non-cancellable toggle event is fired.

Focus Management

An openable container does not result in a change of focus by default. If an author creates an openable component where it would be desirable to automatically move focus when activated, then the autofocus attribute can be used to indicate the element that needs to receive focus, upon opening.

Behaviors

Find in page support

Elements hidden with display:none are not exposed to find in page functionality nor text fragment links. To support use cases where this is desirable, openables support the hidden=until-found attribute. When these are used together, the UA will not apply display:none to the element, but will instead use the content-visibility: hidden from the hidden attribute styles.

Unlike when hidden=until-found is used on its own, the browser will not remove the hidden attribute once the element is found in the page, instead the content-visibility style is reverted through CSS. This allows the element to maintain find-in-page support if it’s opened and collapsed again.

<div id="findable-openable" hidden="until-found" openable>
  Searchable hidden contents
</div>

Accessibility / Semantics

The openable content attribute can be applied to any element, where in the hidden or “collapsed” state, the element will be hidden from the accessibility tree. Upon invoking. the element will become available both visually, and via the browser’s accessibility tree. Beyond this behavior, the element with the openable attribute will otherwise keep its existing semantics and AOM representation. For example, <article openable>...</article> will continue to be exposed as an implicit role=article. Similarly, ARIA can be used to modify accessibility mappings per the allowances defined in the ARIA in HTML specification. For example <div openable role=note>...</div>.

As mentioned in the Declarative Triggers section, accessibility mappings will be automatically configured to associate the openable with its trigger element, as needed.

Disallowed elements

TODO

Example Use Cases

Here, a developer is building a table where additional rows of content can be shown or hidden by use of command buttons.

<table>
  ...
  <tr>
    <th>Name of row</th>
    <td>...</td>
    <td>
       <button commandfor="moar" command="toggle-openable">view additional rows</button>
    </td>
  </tr>
  <tr openable id="moar">...</tr>
  ...
</table>

In the following example, a developer is creating a sidebar navigation where each link has a sibling command button to toggle the visibility of sub-navigation items:

<nav aria-label="secondary">
  ...
  <ul>
    ...
    <li>
      <a href="products.html">Our Products</a>
      <button commandfor="subducts"
        command="toggle-openable"
        aria-label="our products sub-navigation">
       <!-- svg chevron goes here -->
      </button>
      <ul id="subducts" openable>
        <li><a href=...>...</a>
        <li><a href=...>...</a>
        ...
      </ul>
    </li>
     ...
  </ul>
</nav>

The Choices Made in this API

Many decisions and choices were made in the design of this API, and those decisions were made via numerous discussions (live and on issues) in OpenUI, a WHATWG Community Group.

Alternatives Considered

TODO

An alternate to the defaultopen attribute could be to allow the openable attribute to declare its initial state. E.g., <div openable="open"> or similar.

Why a content attribute?

Similar to some of the reasons why popover evolved into an attribute, many types of elements may need to be shown/hidden or “openable” - with no one single element that could represent all scenarios.

As an attribute, a controlling element (button) can toggle the openable state of a list of hyperlinks, a table row, or another section or fieldset of related content, etc. A content attribute allows this behavior to be applied to all of these use cases, and more - allowing the flexibility of developers to use the attribute on any element.

Design decisions (via OpenUI)

TODO

Questions

Do we need a source property in an options bag for the open and toggle methods like popover has?

Should JS methods throw under various circumstances:

  • When the element isn’t connected to the document?
  • When the element’s node document isn’t fully active?
  • When the element is open as a non-modal dialog?
  • When the element is open as a modal dialog?
  • When the element is open as a popover?
  • When the element is a fullscreen element?

Should certain elements be disallowed?

  • For example, should a <details> or <dialog> be excluded?

Should we use a new pseudo class instead of :open as this might conflict with other :open scenarios?

  • Popover uses :popover-open for example.

Does defaultOpen as a separate attribute make sense or should it be a value for the openable attribute?

  • What if we want defaultopen in future for popover?