Ask HN: Help me pick a front-end framework

141 points by bjackman ↗ HN
Hopefully this can be a fruitful discussion since I think I have a quite clear but also reasonably common set of needs. I have a feeling that if you're "in the world" of frontend engineering you get a much more intuitive feeling for these tooling questions than I can get at a distance by reading SEO-spam developer blogs and State Of JS (although the latter does seem really helpful).

I'm thinking of building a text-annotation based app _alone in my spare time_. The core usage loop is about viewing and interacting with "visual markup" applied to a body of text. So lots of tooltips/hoverbars I guess.

Some notes about my needs:

- This would be by far the most complex GUI I've ever built.

- Relatedly, I expect to get the UX design horribly horribly wrong and start from scratch at least once.

- I also suck at making things look pretty and also don't find this problem very rewardinging to think or read about.

- All of the above point towards a framework that is pretty easy to get started in and prototype quickly.

- Because of limited time, and low confidence in UX design decisions, I would often follow the path of least resistance and the end product would be heavily influenced by the tools I used to build it.

----

- Despite what I said about quick rampup I'm fine with learning new languages.

- Weak typing is a deal-breaker. If using JS, must have good TypeScript integration or similar. (Does anyone still write plain JS?)

------

- I don't think I'll want a native desktop app but an iOS/Android version could make sense. But I'll never have time if this can't share most of the code with the web version.

-------

- I don't really have the discipline for manual testing. Luckily I also find it satisfying to over-invest in test automation. I don't really know how this is done in frontend land but good tooling would be a big plus.

-----

- I'm happy to sacrifice one or more of the other requirements in favour of tools that are likely to be maintained 10 years from now.

----

What are HNs thoughts?

180 comments

[ 2.4 ms ] story [ 224 ms ] thread
antd worked well for me
They are all a waste of time.
The important thing is that they waste users' time and not the time of the developer(s).
Steve Jobs was aware of that.

"Well, let's say you can shave 10 seconds off of the boot time. Multiply that by five million users and thats 50 million seconds, every single day. Over a year, that's probably dozens of lifetimes. So if you make it boot ten seconds faster, you've saved a dozen lives. That's really worth it, don't you think?" [1]

[1]: https://www.folklore.org/StoryView.py?story=Saving_Lives.txt

Yes simultaneously thousands of developers are wrong and stupid and have no idea what they are doing, yet you are the enlightened one. Give me a break.
I just said its a waste of thier time. I didnt say they were stupid. You know how many devs have been deprecated in the past year? Millions. Tommorow there will be more, its just how the tools chain swings.
> text annotation app

You could find plain JS to be sufficient for this. I'm guess you're highlighting the text a popup will come up and you submit to a server?

If you really want to use a framework, Svelte could be a good choice. If you want to stay with safer choices then React with Mobx, or something else that uses subscriptions to bypass React's default rendering. The other thing you could do is test out each one and figure out what you like the best, because some of it is subjective.

Web text editing is broken in mobile no? I would at first thinking on builing on prosemirror but it works imperfectly on mobile so I would choose native instead
React with MUI is probably the "safe" choice right now. Getting good performance out of React when you have a lot of things updating in real time can be tricky but it sounds like you probably wouldn't run into that right away in an app like this.
We picked react with mui and indeed have many performance issues; we redid a part with tailwind and vanilla js en the difference in perf is quite bizar. But indeed more (a lot more) work.
Yeah if you want good performance out of a non-trivial React app you need to carefully consider memoization. It can become quite tedious making sure you've memoized the right things at the right places.

MUI also can be slow if you put dynamic stuff in the sx component property. Tailwind has the very nice property that there's no dynamic CSS generation at runtime.

I’ll recommend two paths…

Working alone, there is no easier setup than Laravel and (probably for you) Vue + Inertia. Vue is a major player with lots of available packages and it’s pretty simple to get setup with Laravel for a database and backend. If you need help getting started, Laracasts is unmatched in terms of tutorial quality.

The other path is to use React. If you think you want a job in front end development, spending time in react will broaden your eligible job set by a significant margin. I personally don’t like react, but it’s going to dominate front-end for the next five years at least.

Best of luck!

There's also livewire plus filament admin on the backend. Makes coding a breeze.
(comment deleted)
Sounds like you're in a similar place as me a year ago. Here's a list of tools that I'd use:

- TypeScript for language. Gives you an insanely expressive type system that is a step up from Python duck typing (which you seem familiar with after perusing your GH).

- React for framework. Frontend experts will shit on it for being non-performant or having a poor UX, but the community is massive and you'll have a ton of support both in terms of people that can help you when you're stuck and libraries that do a lot of work for you.

- Jest for code testing and Cypress for integration testing. The obvious tools for the job.

- Chakra UI for a UI framework. Has all the bells and whistles, DX is great, and there are plenty of escape hatches for non-default behavior.

- TipTap for text editor. This is the _most_ at risk of not being supported in 10 years, but I still see it as a low risk because they have a solid monetization strategy in place.

- Electron for the desktop app. If you _really_ don't want to use Electron then use Tauri (what we use at my company). It uses the native browser engine so package sizes are ridiculously small and it's insanely performant.

- Don't worry about the mobile apps for now. At the very most, just make it a web app that has responsive theming for mobile.

Good luck and have fun!

I'd agree with all these, but if I understood correctly OP wants to annotate static text rather than edit.

OP this is an interesting and rather niche area, you will want to be making use of the selection API[0] and figuring out a good way to make it reactive to integrate with react and your data model.

[0]: https://developer.mozilla.org/en-US/docs/Web/API/Selection

Ah, good point, TipTap may be overkill if OP doesn’t want to edit it at all.
For the mobile apps the missing piece of the puzzle is Capacitor.

Also, look for React Testing Library to complement Jest.

You recommend Cypress as an "obvious" choice, so I am curious whether you evaluated Playwright. If you did, why you find Cypress to be the clearly superior choice? Genuine question.
Optimizing for community size and simplicity for an outsider.
Cypress is anything but simple. It takes a lot of time to learn all the footguns and not write flaky tests.
Thanks for the answer! I'm probably personally leaning towards using Playwright for future projects, reading that. I believe Playwright may be passing Cypress in community size, and simplicity is subjective. They both look quite similar, from this outsider's perspective.
Being someone that has recently evaluated the space and chosen Cypress I am curious why you think the community for Playwright may be passing Cypress? That name didn't even come up in my search while I found tons of blogs, discussions, walkthroughs, and discussion around Cypress. So many in fact I walked even confident I had chosen the 'easiest' tool where I classify easy as how hard it will be to find help when I am unclear on how to move forward.
Sure, I can clarify.

1. I know GitHub stars aren't everything, but Playwright has 41.9k at the moment, and Cypress has 40.5k.

2. Playwright is backed by Microsoft, and given how happy I've been with how they manage Typescript, that makes it a pretty safe bet to me.

3. Cypress is much older, so I discount a lot of the blog posts in my evaluations. In fact, I count such things against it sometimes, because oftentimes when I'm hunting for a solution to a problem, all I can find are outdated answers. I run into this a lot with AngularJS and Angular.

4. Playwright is included in most of the recent comparison articles I've run across. I don't really keep track of these, but searching again I quickly ran across 2 examples:

  * https://blog.checklyhq.com/cypress-vs-selenium-vs-playwright-vs-puppeteer-speed-comparison/
  * https://www.testim.io/blog/puppeteer-selenium-playwright-cypress-how-to-choose/
Concur with these suggestions.

TypeScript + React + a good UI library like Chakra will do most of the legwork for you.

For anything where you need to reach into the HTML/JS/CSS, find the right place in the React lifecycle (usually useEffect, useRef, and callbacks).

Prefer closures (I mean: small, local functions), strictly-typed TS interfaces, and async/await for everything. Avoid classes, fancy patterns, ORMs, function overloading, etc. Keep it simple.

Check out RunTypes for TypeScript. It will make moving data into & out of your codes breeze. Just JSON.stringy on one end and JSON.parse then MyRunType.check on the other end.

I have to disagree with the Typescript part. Granted, it's not a bad suggestion, and there's a lot that's good about TS, but this isn't what I would consider a good place for it for a few reasons.

TS, in my experience, has limited benefit when it comes to building a UI, and frontend JS is heavily married to UI programming. For guaranteeing UI functionality, writing years is a better use of one's time. What's more important with UI development is the ability to change visual components fast, and TS often gets in the way of that because it's strict as a feature.

Someone who is deep into TS may have the experience such that they are an exception, and can really benefit from using TS across the board. Since the OP is asking for a frontend framework, I would not assume they are one of these exceptions.

Where TS really shines, IMO, is in business logic and library code, because that's where concrete expectations and promises typically lie. TS is also really good for writing backends for similar reasons.

This might be most appropriate for the OP:

Frontend: Some TS for logic that's well separated from the UI, but don't try to make TS work within code that is rendering stuff. Major frameworks put in lots of effort to support TS in the view layer and, with some exceptions, it usually comes with caveats

Tests: Use JS. Your tests should not be an app in and of itself.

Shared libraries: Use TS only if you prefer it or if other entities are using it, in which case TS helps your code fulfill it's promise to third parties.

And if the OP really ends up hating Typescript, then they should just not use it. TS is not a requirement.

don't try to make TS work within code that is rendering stuff. Major frameworks put in lots of effort to support TS in the view layer and, with some exceptions, it usually comes with caveats

I’m really not sure what problems you have in mind here. I’ve found TS to be really useful with very few caveats.

The biggest problem you’re likely to hit in TS is when things subtly lose their strong typing accidentally -- for example, you need to use some API that works with untyped JSON blobs. But the you’re no worse off than if you were just using plain JS in the first place.

TS is especially great when you’re using libraries with good type annotations -- your IDE can give you useful live typechecking and autocompletion, even if your own code is in JS!

In React code, TS is really good because you can statically check your prop types. There is a little bit of a learning curve to doing that both correctly and tersely, but I think it’s more than worth the effort.

As the OP specifically said they wanted strong typing, TS seems like an obvious choice over vanilla JS. I suppose you could consider other things like Flow or Reason, but TS is the most widely-used by far.

(comment deleted)
OP literally mentions that weak typing is a deal breaker, so they should definitely use typescript.
Honestly, I did not catch that part. I still think it's a poor idea, but that's only my opinion. If they really don't want weak typing, then yeah, TS it is.
For personal projects you should learn how to make TS shut up. I’ve found TS useless on the front end for many places… but for state and utility libraries it’s nice. Hence don’t aim for complete strictness.
That's a good point. TS is still very helpful for catching things before build time, but I don't always want it to actually stop me from doing certain things (which I know can be overridden but that takes more actions).
Sorry, I have to disagree. I used React before with JS and moved to TS, and the difference is just night and day. Forgot a prop? Wrong parameters in a function? Forgot to import a component? TS catches all these while JS doesn't.

What problems specifically do you have with TS on the frontend?

Spot on. 3 years ago I threw a huge tantrum and almost quit my job because they forced us to use TS. Today, I would never code for more than myself without TS. I see it really as more of a "contract system" than a "type system" and that's the real subtle genius of it.
There is not reason whatsoever to use JS over TS for UI. None of the things you said are true.
I agree with you - with the premise of many years of development JS is a much better fit for manipulating the DOM. ClojureScript also accomplishes an isomorphism with what it represents under the hood while being dynamically typed and it seems like people enjoy that DX too.

For a beginner, my experience is that TS is a good way of discovering and working with the complexity and feature set of HTML and web browsers.

IMO if you are using React, you absolutely should be using Typescript for UI on non-legacy codebases. There is most certainly a learning curve in understanding why Typescript seems to show you errors in your UI code, but once you understand the ~5 most common errors it becomes much, much easier to use Typescript instead of JS. I simply won’t use React components (or other libraries for that matter) from NPM if they don’t have Typescript typings (or if I can’t easily create an abstracted shim over the untyped code).

The benefits to using Typescript are immense.

Strongly disagree. TS is very useful for UI, among other things, especially for lots of throw-away or prototype code that changes fast quite often. TS makes it really painless to move and change things around, as opposed to plain JS where things will break quite often on runtime.
To simplify and get started more quickly (JS tooling has become QUITE bloated and complex).

- Install Yarn (package manager) - Run: yarn create react-app THE_NAME_OF_YOUR_APP --template typescript - start hacking

Typescript doesn’t make sense if you don’t manipulate a lot of data or around the app. It can be a pain and lead to a lot of fatigue if don’t get benefits out of it. I am still using js for most of my personal front end projects, and still benefits from intellisense from libraries written in typescript even in standard .js file with VSCode and other IDEs. But 100% of my project uses Eslint (with « recommended » rules + eslint-config-prettier), this is absolutely a must have.
I very strongly disagree with this. Typescript is a lifesaver on a non-trivial React codebase. Writing correct code is hard and you need every advantage you can muster. Type checking is an important part of that.
Look into nullish coalescing and optional chaining. Sure, vanilla JS or react doesn't help with using a var as a function, but I can probably count on my left hand how many times I tried to do that. That doesn't warrant a completely new language. ps.: typeof is also a thing in vanilla JS
I have been writing TS all day every day for the last 5 years. I'm aware of null coalescing and optional chaining.

Every time people tell me "I just don't make type errors" I look through their issue tracker and find them everywhere.

Totally agree, for non trivial project it totally makes sens, but for hobby project it doesn’t always make sens. Even less for beginners like OP.
That "pain and fatigue" is saving you from future bugs you'll inevitably pay for down the line.

Forcing you to be clear and explicit about types and handling cases where "undefined" could exist is a good thing.

It might take longer to develop but I've definitely seen I get fewer run time errors once it builds. No more staring at a blank page and checking the developer console for an undefined that's brought everything to a halt.

Use whatever you like. Is a spare time project, you don't want the tool be an excuse to not start right now. The end user doesn't give a shit about minimal performance wins.
I find Vue easier to learn than React and its documentation is great.
Quasar lets you deploy spa, SSR, electron, android and iOS, reusing what's appropriate. It uses Vue.

But my real advice is go kick the tyres. Try hello world in half a dozen candidates.

Take a thin vertical slice through your app, and try implementing in your short list of frameworks.

It will be worth the investment

OP, I didn’t grok the description of what you’re trying to build enough to have a useful suggestion for you, but as a word of caution you’re probably going to get a lot of responses that don’t take into account you’re specific situation but instead are based on the commenter’s current favorite front end tool. As someone who is pretty new to front end development, you would do well to stick with standard tooling like React. For all its flaws it has a robust community around it that will make it easier for you to get help when you get stuck, and libraries that will do some of the work for you.
How fast can I do the least work?

React.

Never "how can I do the most maintainable code", "how can I write good code".

Use React unless you have a reason not to do so.
I agree with the other comments suggesting React. I actually typically recommend just plain HTML and CSS for most projects. But it sounds like you’re building something with a lot of interactivity, so something like React (or Vue or Svelte) is going to make your life a lot easier.

I also agree with other comments that you should postpone worrying about iOS/Android apps for now. Targeting those platforms adds a lot of complexity and will slow you down. So you should avoid them unless your app really only makes sense as a native mobile app.

If down the road you do decide that you want native mobile apps, I recommend using React Native with Expo. Transitioning from React to React Native is relatively easy (from a concepts perspective). But React Native doesn’t use HTML or real CSS (although it’s heavily inspired by it), so you will need to rewrite a lot of your code and change all of your dependencies over to React Native-compatible versions. With React Native, you can have a single code base that targets web, iOS and Android so you don’t need to maintain multiple versions of your code.

If you don’t like React Native for some reason (e.g., it’s made by Meta), you can also look into Flutter which is made by Google.

Html + CSS + Javascript in 2022 is mostly what you need.

Unless your job is going to require a framework, it’s really not necessary.

Maybe I’m the old curmudgeon who is more concerned about putting out something that works and get customers/revenue quickly, than messing around with the latest Javascript toolchain to ensure it builds in Docker.

You csn get a ton done with Javascript and Tailwind or DaisyUI. In fact almost 90% of what you need.

And with the advent of new backend frameworks like Liveview that patch the dom, I believe things like Angular, React, Vue are on the dying list.

> Unless your job is going to require a framework, it’s really not necessary.

Respectfully disagree. Mirroring your state in the DOM on your own is incredibly easy to get wrong. Doesn't matter if it's react, svelte or vue, any such framework will greatly simplify the UI parts. Aside from that though, I agree you can (and probably should) avoid most deps.

I found using web components with Lit got me most of the way there to allowing individual components to self contain their state and redraw as required when state changed.
For prototyping and simple things, instead of mirroring my state in the DOM, I use the DOM to store state. Yes, it isn't quite as performant, but for this sort of work that doesn't usually matter.
> For prototyping and simple things, instead of mirroring my state in the DOM, I use the DOM to store state.

I find it funny that people think mirroring the DOM will magically make your web app performant. I've visited plenty of React sites that are sluggish, bloated, load slowly and a pain to use. I've also visited plenty of plain ol' HTML/CSS/JS sites that are snappy, load quickly, and fast enough. No framework or clever algorithm will save you from crappy code ;)

I don't think it will magically make your code performant, but I've written things where using the DOM for state was pretty painfully inefficient. For example, multiple queries over a large amount of data where data is only represented as DOM notes. When working on small things and prototypes, though, this has been the exception, and I'm happy to mostly just use the DOM.
I didn’t quite follow the OP’s description of the app, but it sounds like the sort of app where you’ll end up having to do a lot of raw DOM manipulation / selection logic.

I actually think vanilla JS / TS might be the best fit, here. You’ll probably be writing a lot of plain JS anyway based on my understanding of the description, so I’m not sure a framework buys you much.

I highly recommend Tailwind for CSS and Playwright for testing the real UI.

> Because of limited time, and low confidence in UX design decisions, I would often follow the path of least resistance and the end product would be heavily influenced by the tools I used to build it.

Angular with the Angular Material components library would be very "batteries-included". You don't have to decide how you store state, what testing framework to use, etc. Angular provides sane defaults for most things. And it also ticks your TypeScript requirement.

> Relatedly, I expect to get the UX design horribly horribly wrong and start from scratch at least once. I also suck at making things look pretty and also don't find this problem very rewardinging to think or read about.

With angular material, you'll get pre-built and pre-styled navigation menus, form inputs, etc. Wiring them up to your TypeScript code will be a breeze but it'll be tough to customize them if you're going for somewhat unique / unusual behavior or styling. And occasionally you'll need something commonplace like a time picker and the library won't have that.

> I don't think I'll want a native desktop app but an iOS/Android version could make sense.

The simplest route to putting your app in the app store / play store is called a Progressive Web App (PWA). For which you basically need a config json and some icons. The gotcha is that PWAs have virtually zero access to native features.

> I don't really have the discipline for manual testing. Luckily I also find it satisfying to over-invest in test automation. I don't really know how this is done in frontend land but good tooling would be a big plus.

Angular uses Jasmine by default for unit tests. You can test both the business logic and UI interactions this way, but in my experience mocking out components is annoying and a fuckton of maintenance. I usually stick with testing the logic or clumps of un-mocked components. There's also support for end-to-end tests using Protractor, but I'm not super familiar with it.

> I'm happy to sacrifice one or more of the other requirements in favour of tools that are likely to be maintained 10 years from now.

Angular will probably be around 10 years from now, but I doubt keeping the version / other dependency versions up-to-date will be painless. Some things are just kinda deprecated / replaced for lame reasons (but you'll have notice of these changes waaay in advance).

+1 to angular being "batteries included". If you need to ask what framework to use, pick angular as all of the hard choices have already been made.

You don't have to make any more choices after picking angular - you are all set and everything not only works together perfectly complete with extensive documentation for the whole thing, but you also get a professionally maintained UI framework designed to work with it.

Compare with react which is just the first of many subsequent and continual choices you will need to keep on making as sub-frameworks come and go and need to be replaced as you maintain your app e.g. major security issue in foo 1.1 but you can't upgrade as it has a dependency on boo 1.2 that has not been updated so you need to switch to quux 0.9a but that changes how forms are handled so you need to refactor your apps routing, and that breaks your test framework etc, then someone deletes the leftpad repo and none of your dependencies work anymore anyway etc (oh and by the way your NPM install is out of date too so upgrade that first). Then factor in fighting with NPM & node, dealing with separate documentation for different sub-frameworks, your effort and sanity from trying to integrate them etc. I am perplexed as to why anyone would pick react for anything apart from the most trivial of UIs - it is a nightmare.

Angular will be around for a long time - it is used extensively in enterprise environments and Google.are throwing SWEs at it even if the vocal cool kids are using react or Vue or whatever.

I agree. I chose React only for the simple reason there are more React jobs than Angular out there.
Angular markets itself as a tricycle but comes with a manual in which the first chapter after the introduction instructs you to swap out your wheels for jet engines. The learning curve and hidden complexity is absolutely insane and I would only recommend choosing Angular in extreme cases where you actually need to break the sound barrier.

Source: I maintain a complex Angular SPA since AngularJS/2.

Can you elaborate on some of your challenges please?

I find it quite straight forward to use once you overcome the somewhat steep learning curve.

Sure, the following challenges come to mind:

* Must maintain a deep understanding of RxJS, which is its own beast. Angular team sometimes uses it in "creative" ways that change between versions in creative ways.

* Must not go off reservation (or do) where the Angular code is committed but not covered by documentation (because reasons). Template variables (hack an ngIf into an ngVar) come to mind, as well as how not subclass an abstract component (e.g. abstract component can technically have its own template and interesting things happen when children also have their own templates).

* Must understand how Angular i18n works by inspecting various Angular code repositories (and sometimes having to build the Angular project yourself, inadvertently discovering the "wonders" of Bazel). For example, the list of Angular supported locales is generated from CLDR dumps. CLDR likes to release new dumps and so the list of Angular supported locales changes (sometimes). You can think of interview Angular questions from hell such as what is the default build locale and what happens if you change it to "en-US-POSIX"? Or, my current favorite, how to ask Angular put dir="rtl" into the generated index.html for locales that are RTL?

These are just off the top of my head. I didn't even go into various performance optimization techniques which may or may not be made redundant by the next version of Angular because of their own compiler optimizations. Also, having to make the decision of the E2E testing solution (or sticking with Protractor).

While I do agree with you, I must admit that some of these reasons could be subjective. That being said, Angular has its share of issues. However, I find the overall developer experience to be good and I usually find the code to be well organized in projects.
Yeah, this description doesn't sit with me very well, either. I fully support the argument that Angular is not a good choice because React is more popular and has a larger pool of developers, but not that it is more complex.

I too would like to hear about what complexities you are encountering and if they are incidental or necessary complexities.

?

This reads like too much cool aid.

Angular is an entirely application stack, “choices made for you”. uses dependency injection out of the box, has a complex module system (eg. How you used to have import Material components one by one). It’s observables all the way down.

How can you argue that’s not complex? It’s super complex.

I mean, let’s just take an example, say, making a library… https://angular.io/guide/creating-libraries

Now let’s look at https://kit.svelte.dev/docs/packaging

Quite the difference.

I’m a modest fan of angular because it produces good stuff… but not because it’s simple or easy to learn.

You get good solid engineering outcomes with it.

> The gotcha is that PWAs have virtually zero access to native features.

Huh?

Lots been done in this space — what are you still missing?

Based on your requirements and resources I would recommend using plain simple JavaScript over any framework. The time you would spend learning any framework or build system would be better spent iterating on your core experience and experimenting with things that come out-of-the-box on modern web browsers. Frameworks inevitably drag you into their ecosystem where everyone marches at a certain pace. While there are benefits to this, I think it would be a distraction for you. Furthermore, there are many arcane JavaScript libraries that you may find useful down the road, and it is much easier to drag-and-drop them into your project with HTML "<script>" compared to fighting with module bundler du jour.
I would suggest you to take a look at https://svelte.dev/. It's fast, has TypeScript support and is very easy to learn. I've been using React for many years, but after trying out Svelte, I am coming to the conclusion that Svelte is much more fun and more powerful. For example, it supports a global store out of the box. On the other side, it's not as mature as other frameworks, probably. Also, in my experience with React Hooks you tend to get into a very complex dependency and rendering logic and the code can be very hard to understand after many hooks were added (but that's another topic).
I have to say Svelte is my first thought, even though it explicitly misses some of the criteria.

There's a lot of criteria in the original question, which will inexorably lead you to React.

But for learning fast from scratch and doing lots of iteration and rewrites on a very complex UI, Svelte is going to provide a leg up.

The biggest issue is finding a UI library you are happy with, and dealing with inevitable Sveltekit changes. I used tailwind and it integrates ok with Svelte. And I've just resigned myself to dealing with Sveltekit updates.

No more dealing with SvelteKit changes, we've hit RC! :)
If it were up to me, I'd go with the following (mostly based on the largest communities):

- Typescript

- React

- styled-components (I've heard good things about tailwindcss too)

- Jest and react-testing-library for testing

- prettier and eslint for linting

Just throwing it out there in case you'd like to share your code between web, desktop and mobile:

- Electron (desktop)

- React-Native (mobile)

- React-native-web (to bridge it all together)

These are good suggestions. I would be wary of picking something like styled-components upfront, though. If you're using something like Chakra-UI or MUI 5 (my personal favorite), they might use different styling conventions in their docs and you may just find it easier to use whatever they use.
I think dotnet core with blazor is a good option these days.

- C# can be used for all code, frontend and backend.

- Several GUI frameworks/toolkits to choose from (mudblazor, radzen etc)

- Cross-platform

- High quality development tools from several providers

- Code can later be reused in native applications for most platforms

- Lots of documentation and tutorials available.

Try Flutter (Dart). Picked it up for a gig this summer. Strong typing, great performance, diagnostic tools are capable, instant hot reload, and it can be compiled for any platform.

The main factor that might make or break it for your usecase is whether it supports the kind of advanced text layout you might need.

Second the recommendation for Flutter, very robust dev environment.