I think events are a bit unsung and underutilized in a lot of web projects. Events are really powerful and you can build systems with them that can replace proprietary framework features with interoperable protocols.
Context: Components that need a context value can fire an event to request it. Any other element or listener higher in the tree can handle the event and provide a value via the event object. Event are synchronous, so you can get values synchronously. The Web Components Community Group maintains an interoperable context community protocol: https://github.com/webcomponents-cg/community-protocols/blob...
Suspense: Components that have some pending some work, like awaiting data to render, can fire an event to signal that they have pending work. The event can carry a promise, and then a suspense-boundary-like component can handle the event and display a spinner until all the pending work in the tree below it is finished. Another protocol: https://github.com/webcomponents-cg/community-protocols/blob...
Error boundaries: A component can fire an ErrorEvent if it fails to render, and an error boundary component can display the error or some other user affordance.
Child-parent registration: A child component can fire an event to tell some parent that it's available. This is useful for using components as plugins. A <code-mirror> element could have children that provide language support, syntax highlight themes, etc.
Actions: Redux-like actions can be done with events instead. You can build a nice data-down, events-up system this way with very little code and very loose coupling.
Event buses: components can listen for events on a top-level node like document, and they'll receive every event of that type from every other dispatcher.