Skip to content

Commit

Permalink
Add docs for :open pseudo-class
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisdavidmills committed Jan 29, 2025
1 parent 28b9521 commit 00ea451
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 18 deletions.
180 changes: 180 additions & 0 deletions files/en-us/web/css/_colon_open/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
---
title: ":open"
slug: Web/CSS/:open
page-type: css-pseudo-class
browser-compat: css.selectors.open
---

{{CSSRef}}

The **`:open`** [CSS](/en-US/docs/Web/CSS) [pseudo-class](/en-US/docs/Web/CSS/Pseudo-classes) represents an element that has "open" and "closed" states, only when it is currently in the "open" state.

## Syntax

```css
:open {
/* ... */
}
```

## Description

The `:open` pseudo-class selects any element currently in the "open" state, which includes the following elements:

- {{htmlelement("details")}} and {{htmlelement("dialog")}} elements that are in an open state, that is, they hae the `open` attribute set.
- {{htmlelement("input")}} elements that display a picker for the user to choose a value from (for example [`<input type="color">`](/en-US/docs/Web/HTML/Element/input/color)), when the picker is displayed.
- {{htmlelement("select")}} elements that display a drop-down box for the user to choose a value from, when the drop-down is displayed.

Note that the "open" and "closed" states are semantic states, and don't necessary correlate with the visibility of the element in question. For example, a `<details>` element that is expanded to show its content is open, and will be selected by the `details:open` selector, even if it is hidden with a {{cssxref("display")}} value of `none`.

{{domxref("Popover API", "popover")}} element "showing" and "hidden" states are also distinct semantic states, which can coexist alongside "open" and "closed" states. You can use the {{cssxref(":popover-open")}} pseudo-class to target styles at popovers that are in the "showing" state.

## Examples

### Basic `:open` usage

In this example, we have a {{htmlelement("details")}} / {{htmlelement("summary")}} element structure, with some basic styling. We use the `:open` pseudo-class to apply a different foreground and background color to the {{htmlelement("details")}} element when it is in the "open" state.

#### HTML

```html
<details>
<summary>
The answer to the Ultimate Question of Life, the Universe, and Everything
</summary>
42.
</details>
```

#### CSS

```css hidden
body {
font-family: sans-serif;
}
```

```css
details {
border: 1px solid #aaa;
border-radius: 4px;
padding: 0.5em 0.5em 0;
background: #fee;
}

details:open {
padding: 0.5em;
background: purple;
color: white;
}

summary {
font-weight: bold;
margin: -0.5em -0.5em 0;
padding: 0.5em;
}

details:open summary {
margin-bottom: 0.5em;
}
```

#### Result

The result is as follows. Try opening the `<details>` element to see the effect:

{{ EmbedLiveSample("Basic `:open` usage", "100%", "100") }}

### Custom `<select>` styling with `:open`

In this example, we have a basic {{htmlelement("select")}} element with some custom styling applied. The `:open` pseudo-class is used to apply a styling enhancement to its "open" state, that is, when the drop-down is displayed.

#### HTML

There is nothing special about our fruit selector, except that we have put it inside a wrapper element to make it easier to apply generated content to the example ({{cssxref("::after")}} and {{cssxref("::before")}} do not play well with `<select>` elements because their content is fully controlled by the browser).

```html
<p>
<label for="fruit">Choose your favourite fruit:</label>
<div class="select-wrapper">
<select name="fruit" id="fruit">
<option>apple</option>
<option>banana</option>
<option>boysenberry</option>
<option>cranberry</option>
<option>fig</option>
<option>grapefruit</option>
<option>lemon</option>
<option>orange</option>
<option>papaya</option>
<option>pomegranate</option>
<option>tomato</option>
</select>
</div>
</p>
```

> [!NOTE]
> We are not using a multi-line `<select>` (that is, one with the [`multiple`](/en-US/docs/Web/HTML/Attributes/multiple) attribute set) — those tend to render as a scrolling list box rather than a drop-down, so don't have an "open" state.
#### CSS

In the CSS, we set an {{cssxref("appearance")}} value of `none` on our `<select>` element to remove the default OS styling from the select box, and provide some basic styles of or own. We then set a {{cssxref("position")}} value of `relative` on the select wrapper so that we can absolutely position our generated content relative to it.

```css hidden
body {
font-family: sans-serif;
margin: 20px auto;
max-width: 400px;
}
```

```css
select {
appearance: none;
width: 100%;
display: block;
font-family: inherit;
font-size: 100%;
padding: 5px;
}

.select-wrapper {
position: relative;
}
```

We set some generated content on the select wrapper that looks like a collapsed "expansion arrow", and position it to the left of the `<select>`. We then change the generated content on the wrapper to an expanded "expansion arrow", only when the descendant `<select>` is open. This is done using a combination of the `:open` and {{cssxref(":has()")}} pseudo-classes to create a parent selector. We are literally saying "select the `::before` pseudo-element of the wrapper, but only when its descendant `<select>` is open."

```css
.select-wrapper::before {
content: "";
font-size: 0.8rem;
top: 8px;
left: -22px;
position: absolute;
}

.select-wrapper:has(select:open)::before {
content: "";
}
```

#### Result

The result is as follows. Try opening the `<select>` dropdown to see the effect on the expansion arrow:

{{ EmbedLiveSample("Custom `<select>` styling with `:open`", "100%", "200") }}

## Specifications

{{Specifications}}

## Browser compatibility

{{Compat}}

## See also

- The {{htmlelement("details")}}, {{htmlelement("dialog")}}, {{htmlelement("select")}}, and {{htmlelement("input")}} elements
- The {{cssxref(":popover-open")}} pseudo-class
1 change: 1 addition & 0 deletions files/en-us/web/css/pseudo-classes/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ O

- {{CSSxRef(":only-child")}}
- {{CSSxRef(":only-of-type")}}
- {{CSSxRef(":open")}}
- {{CSSxRef(":optional")}}
- {{CSSxRef(":out-of-range")}}

Expand Down
9 changes: 5 additions & 4 deletions files/en-us/web/html/element/details/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ browser-compat: html.elements.details

The **`<details>`** [HTML](/en-US/docs/Web/HTML) element creates a disclosure widget in which information is visible only when the widget is toggled into an "open" state. A summary or label must be provided using the {{HTMLElement("summary")}} element.

A disclosure widget is typically presented onscreen using a small triangle that rotates (or twists) to indicate open/closed status, with a label next to the triangle. The contents of the `<summary>` element are used as the label for the disclosure widget. The contents of the `<details>` provide the {{glossary("accessible description")}} for the `<summary>`.
A disclosure widget is typically presented onscreen using a small triangle that rotates (or twists) to indicate "open"/"closed" state, with a label next to the triangle. The contents of the `<summary>` element are used as the label for the disclosure widget. The contents of the `<details>` provide the {{glossary("accessible description")}} for the `<summary>`.

{{EmbedInteractiveExample("pages/tabbed/details.html", "tabbed-shorter")}}

Expand Down Expand Up @@ -44,7 +44,7 @@ This element includes the [global attributes](/en-US/docs/Web/HTML/Global_attrib
## Events

In addition to the usual events supported by HTML elements, the `<details>` element supports the {{domxref("HTMLElement/toggle_event", "toggle")}} event, which is dispatched to the `<details>` element whenever its state changes between open and closed. It is sent _after_ the state is changed, although if the state changes multiple times before the browser can dispatch the event, the events are coalesced so that only one is sent.
In addition to the usual events supported by HTML elements, the `<details>` element supports the {{domxref("HTMLElement/toggle_event", "toggle")}} event, which is dispatched to the `<details>` element whenever its state changes between "open" and "closed". It is sent _after_ the state is changed, although if the state changes multiple times before the browser can dispatch the event, the events are coalesced so that only one is sent.

You can use an event listener for the `toggle` event to detect when the widget changes state:

Expand Down Expand Up @@ -166,14 +166,15 @@ details > p {
box-shadow: 3px 3px 4px black;
}

details[open] > summary {
details:open > summary {
background-color: #ccf;
}
```

This CSS creates a look similar to a tabbed interface, where clicking the tab opens it to reveal its contents.

The selector `details[open]` can be used to style the element which is open.
> [!NOTE]
> In browsers that don't support the {{cssxref(":open")}} pseudo-class, you can use the attribute selector `details[open]` to style the `<details>` element when it is in the "open" state.
#### HTML

Expand Down
29 changes: 16 additions & 13 deletions files/en-us/web/html/element/dialog/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ When animating `<dialog>`s with CSS transitions, the following features are requ
- [`@starting-style`](/en-US/docs/Web/CSS/@starting-style) at-rule
- : Provides a set of starting values for properties set on the `<dialog>` that you want to transition from every time it is opened. This is needed to avoid unexpected behavior. By default, CSS transitions only occur when a property changes from one value to another on a visible element; they are not triggered on elements' first style updates, or when the `display` type changes from `none` to another type.
- [`display`](/en-US/docs/Web/CSS/display) property
- : Add `display` to the transitions list so that the `<dialog>` will remain as `display: block` (or another visible `display` value set on the dialog's open state) for the duration of the transition, ensuring the other transitions are visible.
- : Add `display` to the transitions list so that the `<dialog>` will remain as `display: block` (or another visible `display` value set on the dialog's "open" state) for the duration of the transition, ensuring the other transitions are visible.
- [`overlay`](/en-US/docs/Web/CSS/overlay) property
- : Include `overlay` in the transitions list to ensure the removal of the `<dialog>` from the top layer is deferred until the transition completes, again ensuring the transition is visible.
- {{cssxref("transition-behavior")}} property
Expand All @@ -314,13 +314,13 @@ The HTML contains a `<dialog>` element, plus a button to show the dialog. Additi

##### CSS

In the CSS, we include a `@starting-style` block that defines the transition starting styles for the `opacity` and `transform` properties, transition end styles on the `dialog[open]` state, and default styles on the default `dialog` state to transition back to once the `<dialog>` has appeared. Note how the `<dialog>`'s `transition` list includes not only these properties, but also the `display` and `overlay` properties, each with `allow-discrete` set on them.
In the CSS, we include a `@starting-style` block that defines the transition starting styles for the `opacity` and `transform` properties, transition end styles on the `dialog:open` state, and default styles on the default `dialog` state to transition back to once the `<dialog>` has appeared. Note how the `<dialog>`'s `transition` list includes not only these properties, but also the `display` and `overlay` properties, each with `allow-discrete` set on them.

We also set a starting style value for the {{cssxref("background-color")}} property on the [`::backdrop`](/en-US/docs/Web/CSS/::backdrop) that appears behind the `<dialog>` when it opens, to provide a nice darkening animation. The `dialog[open]::backdrop` selector selects only the backdrops of `<dialog>` elements when the dialog is open.
We also set a starting style value for the {{cssxref("background-color")}} property on the [`::backdrop`](/en-US/docs/Web/CSS/::backdrop) that appears behind the `<dialog>` when it opens, to provide a nice darkening animation. The `dialog:open::backdrop` selector selects only the backdrops of `<dialog>` elements when the dialog is open.

```css
/* Open state of the dialog */
dialog[open] {
/* "Open" state of the dialog */
dialog:open {
opacity: 1;
transform: scaleY(1);
}
Expand All @@ -338,11 +338,11 @@ dialog {
transition: all 0.7s allow-discrete; */
}

/* Before-open state */
/* Needs to be after the previous dialog[open] rule to take effect,
/* Before "open" state */
/* Needs to be after the previous dialog:open rule to take effect,
as the specificity is the same */
@starting-style {
dialog[open] {
dialog:open {
opacity: 0;
transform: scaleY(0);
}
Expand All @@ -359,20 +359,23 @@ dialog::backdrop {
transition: all 0.7s allow-discrete; */
}

dialog[open]::backdrop {
dialog:open::backdrop {
background-color: rgb(0 0 0 / 25%);
}

/* This starting-style rule cannot be nested inside the above selector
because the nesting selector cannot represent pseudo-elements. */

@starting-style {
dialog[open]::backdrop {
dialog:open::backdrop {
background-color: rgb(0 0 0 / 0%);
}
}
```

> [!NOTE]
> In browsers that don't support the {{cssxref(":open")}} pseudo-class, you can use the attribute selector `dialog[open]` to style the `<dialog>` element when it is in the "open" state.
##### JavaScript

The JavaScript adds event handlers to the show and close buttons causing them to show and close the `<dialog>` when they are clicked:
Expand All @@ -398,7 +401,7 @@ The code renders as follows:
{{ EmbedLiveSample("Transitioning dialog elements", "100%", "200") }}

> [!NOTE]
> Because `<dialog>`s change from `display: none` to `display: block` each time they are shown, the `<dialog>` transitions from its `@starting-style` styles to its `dialog[open]` styles every time the entry transition occurs. When the `<dialog>` closes, it transitions from its `dialog[open]` state to the default `dialog` state.
> Because `<dialog>`s change from `display: none` to `display: block` each time they are shown, the `<dialog>` transitions from its `@starting-style` styles to its `dialog:open` styles every time the entry transition occurs. When the `<dialog>` closes, it transitions from its `dialog:open` state to the default `dialog` state.
>
> It is possible for the style transition on entry and exit to be different in such cases. See our [Demonstration of when starting styles are used](/en-US/docs/Web/CSS/@starting-style#demonstration_of_when_starting_styles_are_used) example for a proof of this.
Expand Down Expand Up @@ -435,11 +438,11 @@ dialog {
animation: fade-out 0.7s ease-out;
}

dialog[open] {
dialog:open {
animation: fade-in 0.7s ease-out;
}

dialog[open]::backdrop {
dialog:open::backdrop {
animation: backdrop-fade-in 0.7s ease-out forwards;
}

Expand Down
8 changes: 7 additions & 1 deletion files/en-us/web/html/element/input/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,7 @@ Inputs, being replaced elements, have a few features not applicable to non form

<table class="no-markdown">
<caption>
Captions super relevant to the
Pseudo-classes relevant to the
<code>&#x3C;input></code>
element:
</caption>
Expand Down Expand Up @@ -871,6 +871,12 @@ Inputs, being replaced elements, have a few features not applicable to non form
containing the invalid control.
</td>
</tr>
<tr>
<td>{{Cssxref(":open")}}</td>
<td>
<code>&lt;input&gt;</code> elements that display a picker for the user to choose a value from (for example <a href="/en-US/docs/Web/HTML/Element/input/color"><code>&lt;input type="color"&gt;</code></a>) — but only when the element is in the "open" state, that is, when the picker is displayed.
</td>
</tr>
</tbody>
</table>

Expand Down
2 changes: 2 additions & 0 deletions files/en-us/web/html/element/select/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ The `<select>` element is notoriously difficult to style productively with CSS.

However, these properties don't produce a consistent result across browsers, and it is hard to do things like line different types of form element up with one another in a column. The `<select>` element's internal structure is complex, and hard to control. If you want to get full control, you should consider using a library with good facilities for styling form widgets, or try rolling your own dropdown menu using non-semantic elements, JavaScript, and [WAI-ARIA](/en-US/docs/Learn_web_development/Core/Accessibility/WAI-ARIA_basics) to provide semantics.

You can also use the {{cssxref(":open")}} pseudo-class to style a `<select>` element when it is in the "open" state, that is, when the drop-down options list is displayed. This doesn't apply to multi-line `<select>` elements (those with the [`multiple`](/en-US/docs/Web/HTML/Attributes/multiple) attribute set) — they tend to render as a scrolling list box rather than a drop-down, so don't have an "open" state.

For more useful information on styling `<select>`, see:

- [Styling HTML forms](/en-US/docs/Learn_web_development/Extensions/Forms/Styling_web_forms)
Expand Down

0 comments on commit 00ea451

Please sign in to comment.