# :loaded, :broken, :wrapped, :filled, :dirty, :offline and :idle selectors

> Fade in images when loaded, hide broken ones, detect wrapped flex items, style filled or changed form fields, and react to offline or idle users — all in CSS.

Source: https://csspp.dev/docs/media-forms/


More states the browser knows but CSS can't test. Each replaces a common event handler or measurement script.

| Selector | Matches | Replaces |
|---|---|---|
| `img:loaded` | The image has finished loading | `onload` handlers for fade-in effects |
| `img:broken` | The image failed to load | `onerror` fallbacks |
| `:wrapped` | A flex or grid item that wrapped below the first row | Measuring item positions after every resize |
| `:filled` | A field has a value (a checkbox or radio is checked) | Floating-label scripts, the `placeholder=" "` trick |
| `:dirty` | A field differs from its original value | "You have unsaved changes" scripts |
| `:offline` | The browser has lost its connection | `online`/`offline` listeners |
| `:idle(30s)` | No mouse, keyboard, touch or scroll input for that long (default 30s) | Inactivity timers |

## img:loaded and img:broken {#images}

```css
img { transition: opacity .4s; }
img:not(:loaded) { opacity: 0; }          /* fade in once loaded */
img:broken { visibility: hidden; }        /* hide the broken-image icon */
.avatar:has(img:broken) { background: url(/img/default-avatar.svg) center/cover; }
```

Write the hidden state as `:not(:loaded)` so images still show in browsers that never run css-pp. Lazy-loaded images (`loading="lazy"`) become `:loaded` when they actually load.

## :wrapped — flex items on a second row {#wrapped}

```css
/* switch to a hamburger menu the moment the links no longer fit on one line */
nav ul { display: flex; flex-wrap: wrap; }
nav:has(li:wrapped) ul { display: none; }
nav:has(li:wrapped) .menu-button { display: block; }
```

An item is wrapped when it starts below the bottom of its first sibling. It is re-checked when the container or the window resizes. There is no reliable way to detect this in native CSS.

> **Avoid loops.** If hiding the wrapped items makes them fit again, the state flips back. Hide the list and show a button, as above, rather than shrinking the items themselves.

## :filled and :dirty — form fields {#forms}

```css
/* floating labels without placeholder tricks */
.field label { translate: 0 1.6em; transition: translate .2s, font-size .2s; }
.field:has(input:filled, input:focus) label { translate: 0; font-size: .8em; }

/* unsaved-changes banner */
form .unsaved { display: none; }
form:has(:dirty) .unsaved { display: block; }
```

Both update as the user types, on `change`, and after a form `reset`. Qualify them with a form element or a class, as above, so css-pp only checks form fields.

## :offline {#offline}

```css
.offline-banner { display: none; }
html:offline .offline-banner { display: block; }
```

## :idle(30s) {#idle}

```css
/* hide video controls and the cursor after 3 seconds without input */
html:idle(3s) .player-controls { opacity: 0; }
html:idle(3s) .player { cursor: none; }
```

Use seconds (`3s`), milliseconds (`500ms`) or minutes (`2m`). Any mouse movement, click, key press, touch or scroll resets the timer.
