Jsx uses properties, not attributes, while pretending that it uses attributes. Html attributes are strings, whereas jsx allows you to pass different data types to child components. Thus, properties. Which is also why is uses camel-cased names, or insists on className. Because these are the names of HTMLElement's properties.
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.
Every JSX implementation uses a mix of setting props & attributes, depending on the specific label, as the DOM can be a bit weird with how some things reflect. Generally properties are preferable to attributes, however.