Skip to content

Commit

Permalink
Rename selectmenu to selectlist (#788)
Browse files Browse the repository at this point in the history
This was resolved here:
#773
  • Loading branch information
josepharhar committed Aug 15, 2023
1 parent 38a350e commit bc3e73e
Show file tree
Hide file tree
Showing 6 changed files with 392 additions and 386 deletions.
391 changes: 391 additions & 0 deletions site/src/pages/components/selectlist.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,391 @@
---
menu: Proposals
name: Selectlist Element (Explainer)
layout: ../../layouts/ComponentLayout.astro
---

import SelectAnatomy from '../../components/select-anatomy'
import Image from '../../components/image.astro'

## Table of Contents

- [Background](#background)
- [Testing out the selectlist element](#testing-out-the-selectlist-element)
- [Anatomy of the selectlist element](#anatomy-of-the-selectlist-element)
- [Styling selectlist](#styling-selectlist)
- [Styling parts of the control](#styling-parts-of-the-control)
- [`:open` and `:closed` pseudo-selectors](#open-and-closed-pseudo-selectors)
- [Using your own markup](#using-your-own-markup)
- [Default behavior](#default-behavior)
- [Slotting your own content](#slotting-your-own-content)
- [Extending the markup](#extending-the-markup)
- [Examples](#examples)
- [Keyboard Behavior](#keyboard-behavior)
- [Form Semantics](#form-semantics)
- [IDL](#idl)
- [`selectedOption` and `value`](#selectedoption-and-value)
- [`disabled`, `form`, `name`, `required`, and `labels`](#disabled-form-name-required-and-labels)
- [`type`](#type)
- [Validity IDLs](#validity-idls)
- [Why not improve `<select>`](#why-not-improve-select)

# Background

The `<select>` element does not provide enough customization for web developers, which leads them to implement their own. These implementations can lead to reduced performance, reliability, and accessibility compared to the native form control elements. More on that is in [Custom Control UI](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/ControlUICustomization/explainer.md#introduction).

## Testing out the selectlist element

`<selectlist>` is currently implemented behind a flag in [Chromium](https://chromestatus.com/feature/5737365999976448). To use it, enable the **Experimental Web Platform features** flag in about:flags.

If you encouter bugs or limitations with the design of the control, please send your feedback by [creating issues on the open-ui GitHub repository](https://github.com/openui/open-ui/issues/new?title=[select]%20&labels=select). Here is a list of [open selectlist bugs in open-ui](https://github.com/openui/open-ui/issues?q=is%3Aissue+is%3Aopen+label%3Aselect).

## Anatomy of the selectlist element

Because the various parts of the selectlist can be styled, it's important to understand the anatomy of this UI control.

<SelectAnatomy />

- `<selectlist>` - The root element that contains the button and listbox.
- `slot=button` - The button element that triggers the visibility of the listbox. If this part is not provided by the author, then selectlist will automatically create one.
- `slot=selected-value` - The element that displays the value of the currently selected option. If this part is not provided by the author, then selectlist will automatically create one. Note that this part does not necessarily have to be placed inside the `slot=button` part (read on for more information about how to slot your own markup).
- `slot=marker` - Something that indicates this button opens a listbox, e.g. an icon. If this part is not provided by the author, then selectlist will automatically create one.
- `slot=listbox` - The wrapper that contains the `<option>`(s) and `<optgroup>`(s). If this part is not provided by the author, then selectlist will automatically create one.
- `<optgroup>` - Optional element which groups `<option>`(s) together with a label.
- `<option>` - Can have one or more and represents the potential values that can be chosen by the user.

## Styling selectlist

Selectlist provides a variety of tools to help with styling, including pseudo-selectors for different states and pseudo-elements for different parts.

### Styling parts of the control

One way to style the control to match your requirements is to use the CSS `::part()` pseudo-element to select the different parts within the control's anatomy that you wish to style.

```html
<style>
.my-select-list::part(button) {
color: white;
background-color: #f00;
padding: 5px;
border-radius: 5px;
}
.my-select-list::part(listbox) {
padding: 10px;
margin-top: 5px;
border: 1px solid red;
border-radius: 5px;
}
</style>
<selectlist class="my-select-list">
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</selectlist>
```

The above example results in the following style:

<Image
src="/images/selectlist-styling-parts.png"
alt="The rendering of a selectlist control with the above HTML and CSS code"
/>

`::part()` can be used to style the `button`, `selected-value`, and `listbox` parts of the control.

### `:open` and `:closed` pseudo-selectors

The selectlist element supports the `:open` and `:closed` pseudo selectors as specified by CSS here: https://drafts.csswg.org/selectors/#open-state

When the selectlist's listbox is showing, the selectlist element will match `:open`, and when it is not showing, it will match `:closed`. Here is an example which makes the button red when the listbox is closed and green when it is open:
```html
<selectlist>
<button id=custombutton slot=button behavior=button>
<span behavior=selected-value></span>
</button>
<option>one</option>
<option>two</option>
</selectlist>
<style>
selectlist:open #custombutton {
background-color: green;
}
selectlist:closed #custombutton {
background-color: red;
}
</style>
```

## Using your own markup

You can further customize the control to fit your needs by providing your own markup to replace the default one, and extend and re-order the parts.

### Default behavior

The default behavior of the `<selectlist>` control mimics the behavior of the `<select>` control. You can use it just like a native `<select>`, with the following minimal markup:

```html
<selectlist>
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</selectlist>
```

When doing so, the default button, select-value, and listbox are created for you.

### Slotting your own content

A `<selectlist>` has named [slots](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots#adding_flexibility_with_slots)
that can be referenced to replace the default parts.

For example, to replace the default button with your own, you can do the following:

```html
<style>
.my-custom-select [slot='button'] {
display: flex;
align-content: center;
}
.my-custom-select button {
padding: 5px;
border: none;
background: #f06;
border-radius: 5px 0 0 5px;
color: white;
font-weight: bold;
}
.my-custom-select .label {
padding: 5px;
border: 1px solid #f06;
border-radius: 0 5px 5px 0;
}
</style>
<selectlist class="my-custom-select">
<div slot="button">
<button behavior="button">Open</button>
<span class="label">Choose an option</span>
</div>
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</selectlist>
```

The `slot="button"` attribute on the outer `<div>` tells the `<selectlist>` to replace its default button with the contents of that `<div>`.

The `behavior="button"` attribute on the inner `<button>` tells the browser that this element is what we want to use as the new button. The browser will automatically apply all the click and keyboard handling behavior to this element as well as the appropriate accessibility semantics.

The above code snippet results in the following style:

<Image
src="/images/selectlist-replacing-button-part.png"
alt="The rendering of a selectlist control with the above HTML and CSS code"
/>

Note that the `slot` and `behavior` attributes can be used on the same element too.

You can replace the default listbox part in a similar fashion:

```html
<style>
.my-custom-select [popover] {
width: 300px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 10px;
padding: 10px;
box-shadow: none;
margin: 10px 0;
border: 1px solid;
background: #f7f7f7;
}
</style>
<selectlist class="my-custom-select">
<div slot="listbox">
<div popover="auto" behavior="listbox">
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
<option>Option 4</option>
<option>Option 5</option>
</div>
</div>
</selectlist>
```

The element with `behavior="listbox"` is required to have the [popover attribute](https://html.spec.whatwg.org/multipage/popover.html#the-popover-attribute) set to `"auto"`. Applying `behavior="listbox"` tells the browser to apply select list listbox behavior to that element: it will be opened when the `<selectlist>`'s button is clicked, and the user can select `<option>`s inside it with mouse, arrow keys, and touch.

The above code snippet results in the following style:

<Image
src="/images/selectlist-replacing-listbox-part.png"
alt="The rendering of a selectlist control with the above HTML and CSS code"
/>

### Extending the markup

Not only can you replace the default parts with your own, you can also extend the control's markup by adding new elements. This can be useful to augment the listbox or button with extra information, or to add new functionality.

Consider the following example:

```html
<style>
.my-custom-select [slot='button'] {
display: flex;
align-items: center;
gap: 1rem;
}
.my-custom-select button {
border: none;
margin: 0;
padding: 0;
width: 2rem;
height: 2rem;
border-radius: 50%;
display: grid;
place-content: center;
}
.my-custom-select button::before {
content: '\25BC';
}
.my-custom-select [popover] {
padding: 0;
}
.my-custom-select .section {
padding: 1rem 0 0;
background: radial-gradient(ellipse 60% 50px at center top, #000a 0%, transparent 130%);
}
.my-custom-select h3 {
margin: 0 0 1rem 0;
text-align: center;
color: white;
}
.my-custom-select option {
text-align: center;
padding: 0.5rem;
}
</style>
<selectlist class="my-custom-select">
<div slot="button">
<span class="label">Choose a plant</span>
<span behavior="selected-value" slot="selected-value"></span>
<button behavior="button"></button>
</div>
<div slot="listbox">
<div popover="auto" behavior="listbox">
<div class="section">
<h3>Flowers</h3>
<option>Rose</option>
<option>Lily</option>
<option>Orchid</option>
<option>Tulip</option>
</div>
<div class="section">
<h3>Trees</h3>
<option>Weeping willow</option>
<option>Dragon tree</option>
<option>Giant sequoia</option>
</div>
</div>
</div>
</selectlist>
```

Using custom markup to wrap the list of options, the above example creates sections with their own styles and content as seen below:

<Image
src="/images/selectlist-using-custom-markup.png"
alt="The rendering of a selectlist control with the above HTML and CSS code"
/>

## Examples

You can find multiple examples of `<selectlist>` on our [demo page](https://microsoftedge.github.io/Demos/selectmenu/).

## Keyboard Behavior

The selectlist element has keyboard behavior inspried by both the existing `<select>` element and the [Aria Authoring Practices Guide Combobox Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/). This behavior was decided in [issue 386](https://github.com/openui/open-ui/issues/386) and [issue 433](https://github.com/openui/open-ui/issues/433).

When the listbox is closed and the button is focused:
* Spacebar opens the listbox.
* Enter submits the form associated with the selectlist if one exists. Otherwise, enter does nothing.
* The up, down, left, and right arrow keys all open the listbox without changing the selcted value.

When the listbox is open:
* Escape closes the listbox without changing the selected value.
* Spacebar is used for typeahead.
* Enter changes the selected value to the currently focused option and closes the listbox.
* The up arrow key moves focus backwards in the list of options and changes the selected value to the newly selected option.
* The down arrow key moves focus forwards in the list of options and changes the selected value to the newly selected option.
* The left and right arrow keys do nothing.

## Form Semantics

The selectlist element is a [form-associated element](https://html.spec.whatwg.org/multipage/forms.html#form-associated-element), which means that it can be used to build and submit forms just like the existing `<select>` element.

### IDL

The following IDL is used for selectlist. Most items which are named the same as items for other form-associated elements behave in the same way.

```webidl
interface HTMLSelectListElement : HTMLElement {
readonly attribute boolean open;
readonly attribute HTMLOptionElement? selectedOption;
attribute DOMString value;
[CEReactions, Reflect] attribute boolean disabled;
readonly attribute HTMLFormElement? form;
[CEReactions, Reflect] attribute DOMString name;
readonly attribute DOMString type;
[CEReactions, Reflect] attribute boolean required;
readonly attribute boolean willValidate;
readonly attribute ValidityState validity;
readonly attribute DOMString validationMessage;
boolean checkValidity();
boolean reportValidity();
void setCustomValidity(DOMString error);
readonly attribute NodeList labels;
};
```

### The `selectedOption` and `value` attributes

`selectlist.selectedOption` returns the currently selected `<option>` element. This is unlike `<select>` which has a `selectedOptions` property and returns a list of selected options because selectlist only supports one selected option at a time.

Just like the `<select>` element, `selectlist.value` returns the `value` property of the currently selected option.

### The `disabled`, `form`, `name`, `required`, and `labels` attributes

These IDL attributes behave exactly as they do for other form-associated elements as specified by HTML:
* [HTML spec for `disabled`](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#enabling-and-disabling-form-controls:-the-disabled-attribute)
* [HTML spec for `form`](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fae-form)
* [HTML spec for `name`](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-name)
* [HTML spec for `required`](https://html.spec.whatwg.org/multipage/form-elements.html#attr-select-required)
* [HTML spec for `labels`](https://html.spec.whatwg.org/multipage/forms.html#dom-lfe-labels)

### The `type` attribute

Like other form control elements, selectlist supports the `type` attribute. It simply returns `"selectlist"`.

### Validity IDLs

Like other form control elements, selectlist supports validity checking attributes and methods as specified by HTML:
* [HTML spec for `willValidate`](https://html.spec.whatwg.org/#dom-cva-willvalidate)
* [HTML spec for `validity`](https://html.spec.whatwg.org/#dom-cva-validity)
* [HTML spec for `validationMessage`](https://html.spec.whatwg.org/#dom-cva-validationmessage)
* [HTML spec for `checkValidity()`](https://html.spec.whatwg.org/#dom-cva-checkvalidity)
* [HTML spec for `reportValidity()`](https://html.spec.whatwg.org/#dom-cva-reportvalidity)
* [HTML spec for `setCustomValidity()`](https://html.spec.whatwg.org/#dom-cva-setcustomvalidity)

## Why not improve `<select>`

Instead of creating a new `<selectlist>` element, we could improve the existing `<select>` element to meet the same needs. However, there are a few problems with this (borrowed from the [Custom Controls UI Explainer](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/ControlUICustomization/explainer.md)):

- Changing the parsing behavior for `<select>` to allow other element children by default would likely break a lot of old sites that depend on the current behavior.
- While it might be possible to modify the parser to change rules based on the existence of an opt-in attribute, there would be additional work to be done if JavaScript added or removed that attribute after the fact.
- The `<select>` popup has the capability to break outside the browser window, and `<selectlist>` will not. If the author wants this capability, they will have to stick with `<select>`. If the author would rather have custom styling, then they should use `<selectlist>`. This is a big differentiation and is more than just an opt-in for styling, so havingt a separate tag name than an opt-in attribute makes more sense.

Given that we would be required to introduce something new into the platform (attribute, element, etc) to opt-in to the new behavior; we opted for a new element. This comes with the added benefit that over the years most developers that want customization of controls and components will not leverage `<select>` at all; akin to how developers leverage `grid` and `flex` display types for layout instead of `<table>` or `float`. Additionally, this has the potential to simplify the implementation cost since we do not have to make substantial changes to the current `<select>` parser
Loading

0 comments on commit bc3e73e

Please sign in to comment.