This is a tangent, but the implicit assumption that the UI is visual is just begging for a response from an accessibility perspective, so here goes.
Accessibility is very much an afterthought in native GUIs, not only in Cocoa, but also in Windows with the UI Automation API, and AFAIK with other native accessibility APIs as well. With these APIs, the assistive technology (e.g. screen reader) pulls information from the application (usually via the GUI toolkit), through repeated calls to methods defined by the accessibility API. Often the AT has to do several such calls in a row (and those often translate to multiple IPC round trips, making things slow). And the UI might change between such calls; there's no guaranteed way to get a consistent snapshot of the whole thing, as there is with a visual frame. On the application/toolkit side, these methods may return different responses from one call to the next, and the application or toolkit has to fire the right events when things change.
The web improves on this, in that accessibility information is conveyed through HTML tags and attributes. And yes, this is included in the output of a React component's render function. So while in practice, implementing accessibility may still be an afterthought, it's not an architectural afterthought as it is in native platforms.
One of my goals in AccessKit [1] is to work around this shortcoming of native accessibility APIs, particularly for developers of cross-platform non-web GUI toolkits. In AccessKit, the toolkit pushes a full or incremental accessibility tree update to the AccessKit platform adapter, which maintains the full tree in memory and uses that to implement the platform accessibility API. This even works for immediate-mode GUIs, as one can see in my proof-of-concept integration with the Rust egui toolkit [2].