Those are React optimizations/restrictions, not JSX restrictions. They want to make diff/patch as fast/simple as possible and rely on properties to do that as much as possible. `className` might have been a restriction in early JSX parsers trying to avoid keyword clashes, but every current JSX parser (especially Typescript) has no problems with JS keywords embedded in JSX. (I don't think it actually was a restriction at any point, but early JSX parsers might have been conservative in how they wanted to parse given the fate of E4X.)
Snabbdom [1] takes an approach of explicitly separating `props` and `attrs` at the expense of extra verbosity in the JSX `<span props={{ className: 'example' }} attrs={{ 'aria-label': 'Example'>Example</span>`. My Butterfloat [2] uses simple heuristics, starting with the props because you can conditionally type a ton of JS type data (and MDN link comments) out of TS DOM types for a strong autocomplete experience, adding common important-to-HTML copy/paste shortcuts such as `class` and `for` and switching to attributes if an XML namespace seems to be in use or the name includes a `-` and seems to be qebab-cased rather than camel-cased.
Snabbdom [1] takes an approach of explicitly separating `props` and `attrs` at the expense of extra verbosity in the JSX `<span props={{ className: 'example' }} attrs={{ 'aria-label': 'Example'>Example</span>`. My Butterfloat [2] uses simple heuristics, starting with the props because you can conditionally type a ton of JS type data (and MDN link comments) out of TS DOM types for a strong autocomplete experience, adding common important-to-HTML copy/paste shortcuts such as `class` and `for` and switching to attributes if an XML namespace seems to be in use or the name includes a `-` and seems to be qebab-cased rather than camel-cased.
[1] https://github.com/snabbdom/snabbdom?tab=readme-ov-file#jsx
[2] https://worldmaker.net/butterfloat/