Why kebab-case non-standard attributes are allowed while others aren't? And how to define types like this in TypeScript? Why kebab-case non-standard attributes are allowed while others aren't? And how to define types like this in TypeScript? vue.js vue.js

Why kebab-case non-standard attributes are allowed while others aren't? And how to define types like this in TypeScript?


The answer is in the JSX section of the Typescript Handbook:

If an attribute name is not a valid JS identifier (like a data-* attribute), it is not considered to be an error if it is not found in the element attributes type.

The entire section on JSX is a very enlightening read.

I'm afraid the answer to the "how to define types like this in TypeScript" question is... We don't. JSX support is built into the compiler. There's plenty of interesting things we can customize, however.


The TS handbook note in Fabio's answer explained all of this, just want to expand a little bit. In short, kebab-case attributes are not considered valid by TS but will not throw error; but attribute prefixed by data- or aria- are considered valid.

React (since 16) accepts custom attributes, i.e <div foo /> and <div whateverYouLike={2}> should work.

What I find confusing with React, is that data-* and aria-* should be written as-is, vs. converting them to camelCase like everything else. Especially when these attributes are converted into camelCase in vanilla DOM:

<div data-my-age="100" aria-label="A Test" />
const $div = document.querySelector('#test')$div.dataset.myName = "D"console.log({ dataset: $div.dataset }) // { myAge: "100", myName: "D" }console.log($div.ariaLabel) // "A Test"

There're no reasons ever given for this, so we can only speculate. Perhaps something to do with a11y toolings, parsing convenience, etc.

The reasons that <div foo /> throws in TS is because TS provides a strict set of valid property names. However, as noted by the other answer, TS will not throw error on random-foo because it is considered an invalid JS identifier. My speculation is because DOM elements allow arbitrary properties, so this is a compromise that allow correct typing in most cases in TS but provide some sort of escape hatch. Would love to know the reasons behind these decisions.

How to define types like this in TypeScript? i.e. Only allowing standard or kebab-case attributes.

As Fabio has already pointed out, JSX support is built into the compiler. However, beside the ability to identify what constitute a valid attribute name, I don't think there's a lot of magic to it: there's a comprehensive list of valid DOM attributes. TS doesn't throw error if you mix kebab & camel cases, i.e <div data-myName> work, <div myName/> doesn't, etc., so it does not differ by casing either.

If you know all your valid props in advance, you can emulate the same thing.

// allow only these prop names, which happened to be all camelCasedinterface MyThing {  name: string  myName: string  anotherProp: string}

In case of kebab-case, template literal types could be helpful:

type ValidPrefix = "data" | "aria";type ValidSuffix = "banana" | "apple" | "pear";type ComputedProps = {    [key in `${ValidPrefix}-${ValidSuffix}`]?: string;};const x: ComputedProps = {    "data-apple": 'hi'};

Beyond this, there's currently no mechanism in TS that can differ between camelCase & kebab-case string.


If you're looking for a way to augment JSX to allow custom props and custom elements, this is a way to do it:

Augmenting JSX attribute to allow custom props & custom elements