Building a webpage node by node from within Javascript does not lessen the complexity of HTML and CSS, it simply adds to it. You're doing the exact same things - creating DOM nodes with attributes - but with yet another level (or 5) of indirection.
Excess layers of indirection come when you try do view generation on both the server and the client.
Many server frameworks currently follow this approach. Generate the view scaffold using templates on the server-side. Then request partials asynchronously on the client and generate additional views client-side using a completely different framework.
Shifting all of the view generation to the client frees up the back-end devs to focus all of their time/effort on the important part. Building and scaling the data infrastructure.
Not to mention, shifting the view layer client-side reduces resource requirements on the back-end.
The previous generation of front-end SPA frameworks frankly sucked in terms of performance, leading to a bad user experience. There were 2 primary reasons why. All data persistence/binding happened directly on the DOM, and all execution was handled on the main execution thread.
The latest generation greatly improves the experience by using new techniques such as data persistence/diffing via the virtual DOM, and handling all non-rendering logic on a background worker thread.