type KebabCase<S extendsstring> =
S extends`${infer H}${infer R}`
? R extends Uncapitalize<R>
? `${Lowercase<H>}${KebabCase<R>}`
: `${Lowercase<H>}-${KebabCase<R>}`
: S
The common wrong answer
type KebabCase<S extendsstring> = Lowercase<S>
`Lowercase` changes letters but forgets where the word boundaries were. Once every capital is gone there is no information left from which to insert the hyphens.
Line by line
R extends Uncapitalize<R>
The suffix begins lowercase when uncapitalizing it changes nothing. If it changes, its first character marks a new word.
Takeaway
Case conversion must detect boundaries before normalising case; transformation destroys the evidence you need for parsing.