- From: Daniil Sakhapov via GitHub <noreply@w3.org>
- Date: Wed, 23 Jul 2025 14:54:31 +0000
- To: public-css-archive@w3.org
My previous rough draft was based on adding pseudo-elements to the event path and having them participate in bubbling. After talking with the WHATNOT WG and hearing all the feedback, especially around the huge web-compat risks with `mouseover`/`mouseout` events (e.g. hovering mouse over pseudo-elements and real element would fire too many events), it's clear that my first approach was too complex and risky. So, I've rethought it and would like to propose a much simpler and safer alternative. ### New Proposal: A "Contained Event" Model The idea is simple: when an event happens on a pseudo-element, it fires a **self-contained event** that only its own listeners can hear. * The event **does not bubble** up to the originating element. It lives and dies with the pseudo-element. * To give developers context, we'd add a new `event.pseudoTarget` property that points to the pseudo-element handle. This means the main event dispatch for the real element is **completely untouched**, which guarantees web compatibility. It neatly solves the `mouseover` problem because the pseudo's `mouseover` is a separate event that old code will never see. **Here’s how it would work for the `::scroll-marker` use case:** ```javascript const list = document.querySelector('#firstItem'); const scrollMarker = list.pseudo('::scroll-marker'); // The API is simple and does what you'd expect: scrollMarker.addEventListener('click', (event) => { console.log('Scroll marker was clicked!'); // event.target would still be the #firstItem if it bubbled, // but this event doesn't bubble at all. console.log(event.pseudoTarget); // The CSSPseudoElement for ::scroll-marker }); ``` *** ### The Important Limitations: Where This Model Breaks Down Now, this model is safer, but it isn't solving everything. Here are some examples of unsolved problems. #### 1. Default event handling Because the pseudo-element's event is completely separate, it can't affect the behavior of its real parent element. * **Preventing Navigation:** If you have a `::before` on an `<a>` tag and call `event.preventDefault()` in the pseudo's `click` listener, the link **will still navigate**. The `preventDefault()` call only affects the contained event, not the separate, standard event that fires on the `<a>` tag. * **Form Submission:** Likewise, if you style a pseudo-element to look like a submit button inside a `<form>`, clicking it **will not submit the form**. It's not a real form-associated element, and the click event doesn't trigger the form's submit action. #### 2. Accessibility Issues This is probably the most critical issue. The model encourages building components that are inaccessible to many users. * **Invisible to Screen Readers:** Interactive logic attached to a pseudo-element is a black box for assistive technologies. A screen reader builds its understanding from the DOM via the **accessibility tree**, and pseudo-elements aren't really there now. Though, probably, it can be fixed with spec changes? * **Keyboard Unreachability:** Since generally a pseudo-element can't get focus, it's unreachable via keyboard navigation. Any `click` listener on a pseudo is therefore **mouse-only logic**. This creates a significant barrier for users who rely on keyboards. #### 3. Stateful UI The model works for simple, one-off clicks but breaks down for any stateful UI interaction. * **Drag and Drop:** Trying to use a `::before` as a "drag handle" is problematic. The `dragstart` event on the pseudo is disconnected from the element you actually want to drag. #### 4. Ambiguous in Complex Layouts and Debugging The model's simplicity creates ambiguity when interacting with other core browser systems. * **Overlapping:** If you have multiple overlapping pseudo-elements with listeners, it's not clearly defined which one should receive the event, or if the parent should also get an event. Hit-testing rules become complex and unpredictable for developers. * **Debugging:** Calling `event.composedPath()` on an event from a pseudo-element would be useless for debugging. It would show a very short path (maybe just the pseudo itself?), hiding its actual context within the larger DOM tree. *** ### New Recommendation: Start Small and Safe Given these significant limitations, I don't think we should try to make all events work on all pseudo-elements. Instead, I propose we take a cautious **"allow-list" approach**. We should start by enabling only a few simple, stateless events like `click` on specific pseudo-elements where we know it's safe and solves a real problem (like `::scroll-marker`). -- GitHub Notification of comment by danielsakhapov Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/12163#issuecomment-3108997256 using your GitHub account -- Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config
Received on Wednesday, 23 July 2025 14:54:32 UTC