I just use vanilla JS. For transport I use JSON over Websockets (with fallback to long-polling if the user is behind a proxy that doesn't support Websockets). I format the messages like id+command+json-payload. If the message contains an id, it calls the callback function given by the API request. If no id is given, the event listeners for "command" are called. In a higher level there are more events that can be listened for.
For layout I use a mix of both pure and imperative functions that either creates a new element for every state, or modifies existing elements - however all isolated in a component or widget.
I try to use standard HTML5 elements rather then creating my own. But they are often inside a component or widgets which takes care of events like keypress, mousedown etc.
There are no string templates! No server rendering, just API endpoints. There is no JQery, there is no framework - besides the functionality for handling server messages and passing data to event listeners and callbacks.
Some components and widgets are generic and can be reused in other projects. But most are specific to the app itself.
The advantages is that it's simple, fast, and customizable. The disadvantage is that layout is a bit tedious using appendChild instead of XML/JSX.
I use CSS for styling. I like CSS very much probably because I used to do web apps before CSS existed. To change theme you just change the .css file. Components, widgets and elements are not aware of theme and style, that's all handled by CSS.
Animations are handled by CSS, and different screen sizes are also handled by CSS. Sometimes you need to the change components/widgets though.
The only advantage I see with frameworks is that you get to write XML/JSX/HTML, which makes it more easy to move things around vs just using JS functions and appendChild. But when looking at React apps the components are broken down into individual files anyway, so it's difficult to get an overview.
Probably the biggest advantages of going vanilla JS eg. no JSX nor frameworks, is that debugging becomes easier, and you do not need a build pipeline, just refresh or hot-reload individual functions. No bundles or package managers needed.