timer = setInterval(tick, 1000)}
onremove={() => clearInterval(timer)}>
```
### Animating removal
The runtime detaches a node synchronously, so a leave animation is driven by
reactive state, not by `onremove`. Keep the row in the list, flip a reactive
flag that toggles a CSS class, let the animation play, and do the real removal
when it ends.
```html
function startLeaving(m: Message) {
m.leaving = true;
}
)>
{
if (m.leaving) {
messages.delete(m);
}
}}>
{m.body}
startLeaving(m)}>delete
```
```css
.message.is-leaving {
animation: leave 200ms ease forwards;
}
@keyframes leave {
to {
opacity: 0;
transform: translateX(1rem);
}
}
```
`m.leaving` is transient client-side UI state (declare it as `leaving?: boolean`
on the row type). Setting it re-runs the `class` binding, so `is-leaving` lands
and the CSS animation plays. `onanimationend` then performs the real removal:
`messages.delete(m)` for a LiveTable, or a splice for a plain array. The row
stays mounted the whole time because it is still in the collection. The
`if (m.leaving)` guard keeps an enter animation on the same element from
triggering the delete.
**Use element-attribute handlers (`onclick={...}`) over `addEventListener`** for
events Elements supports as attributes. The attribute path goes through the
runtime's event wiring; `addEventListener` bypasses it and can surface as inert
behavior under iOS quirks like passive-listener and capture-vs-bubble. Reach for
`addEventListener` only for events not supported as attributes (`popstate`,
`hashchange`) or page-wide listeners that must live above any single template.
**Don't put `ontouch*` on `` or other broad ancestors.** When iOS Safari
sees a non-passive touch listener on an ancestor, it delays or eats clicks on
child elements while waiting to see if the listener will preventDefault. Bind on
the smallest reasonable target.
**Clipboard UI must not gate on the write succeeding.**
`navigator.clipboard.writeText` throws synchronously on insecure origin (iOS
Safari over LAN). A `.catch()` won't catch it. Set the "copied!" UI state first,
then attempt the write inside try/catch.
## Focus
`focus={expr}` is a reactive focus binding.
```html
```
On initial render, truthy translates to `autofocus`. Subsequent flips call
`focus()` or `blur()`.
**iOS focus footgun.** iOS Safari only opens the soft keyboard when `focus()`
runs on an input that was already in the DOM at the start of the user gesture.
Inputs mounted via `e:if` mid-gesture are out-of-gesture and silently denied.
Two fixes:
1. Always-mount the input. Hide it with `visibility: hidden`. Don't use
`display: none`, which also disqualifies focus.
2. Use `flush()` to mount the input synchronously inside the gesture.
**Focusable elements only.** `onfocus`/`onblur` won't fire on a `
` or
`
` unless `tabindex={0}` is set.
## Flush
`flush()` synchronously drains pending reactive updates. Use only when a browser
API requires the DOM to update mid-handler. The most common case is iOS
soft-keyboard focus inside a click handler.
```ts
import { flush } from "@elements/app";
function openSearch() {
open = true;
flush();
inputEl.focus();
}
```