Uncapitalize<StringType>

Lowercases the first character of a string literal type, leaving the rest untouched.

Signature
type Uncapitalize<S extends string> = intrinsic;
  • Added in TypeScript 4.1

Parameters

ParameterTypeDescription
$SstringA string literal type or a union of them. Only the first character is affected.

Return value

A string literal type with the first character lowercasedThe literal type with only its first character lowercased. Distributes over unions; the empty string literal is returned unchanged.

Errors

tsc errors if the argument does not satisfy the `string` constraint.

Changelog

  • PHP 4.1Introduced with template literal types. Implemented as a compiler intrinsic using `str.charAt(0).toLowerCase() + str.slice(1)`.

Uncapitalize is the inverse of Capitalize: first character down, rest untouched. On its own it is unremarkable. Paired with infer in a template literal type it becomes the tool for going backwards from a method name to a property name.

The pattern is: match the prefix, capture the remainder, fix its casing. S extends `get${infer R}` ? Uncapitalize<R> : never turns "getName" into "name". That single conditional is what lets you derive a state shape from an accessor interface, recover prop names from onClick-style handlers, or map a class's handleSubmit methods back to the events they respond to — without maintaining the second list by hand.

What trips people up

  • infer R after a prefix gives you the raw remainder, which is still capitalised. Forgetting the Uncapitalize leaves you with "Name", and the resulting object keys silently do not match your data.
  • It is not the exact inverse of Capitalize for every input — a string that already begins lowercase, or with a digit, comes back unchanged, so the round trip is lossy.
  • Only the first character changes. Uncapitalize<"HTTPServer"> is "hTTPServer", which is almost never what an acronym-heavy codebase wants.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Derive a state object's field names from a union of `getX` accessor names.
  • Recover prop names from `onEvent`-style handler keys with `infer` plus a prefix match.
  • Map class method names such as `handleSubmit` back to the action names they handle.

Frequently asked questions

How do I turn getName into name at the type level?

Match and capture the remainder, then lowercase its first character: `S extends `get${infer R}` ? Uncapitalize<R> : never`. Without the `Uncapitalize` you would get `"Name"` with the capital intact.

Is Uncapitalize the exact inverse of Capitalize?

Not always. It only changes the first character, so a literal that already starts lowercase or with a non-letter round-trips unchanged, and acronyms like `"HTTPServer"` become `"hTTPServer"` rather than `"httpServer"`.

Does Uncapitalize work on a union of string literals?

Yes — it distributes over the union, so `Uncapitalize<"Name" | "Email">` is `"name" | "email"`. That is what makes it usable in the key-remapping clause of a mapped type.

More examples

Round-tripping props to handler names and back

`Capitalize` builds the handler keys and `Uncapitalize` recovers the original event union from them, so the two types stay in sync from a single source. Output: `[ 'onClick', 'onSubmit' ]` then `clicked submit`.

Example · typescript
type Event = "click" | "submit";
type HandlerName<E extends string> = `on${Capitalize<E>}`;
type EventOf<H extends string> = H extends `on${infer R}` ? Uncapitalize<R> : never;

type Handlers = { [E in Event as HandlerName<E>]: () => string };

const handlers: Handlers = {
  onClick: () => "clicked",
  onSubmit: () => "submitted",
};

const back: EventOf<keyof Handlers & string> = "submit"; // "click" | "submit"

console.log(Object.keys(handlers).sort());
console.log(handlers.onClick(), back);

Building a state shape from an accessor interface

`State<Store>` resolves to `{ userId: number; theme: "light" | "dark" }` — `reset` is filtered out because it does not match the `get` prefix, and each value type comes from the accessor's return type. Output: `42 dark` then `[ 'theme', 'userId' ]`.

Example · typescript
interface Store {
  getUserId(): number;
  getTheme(): "light" | "dark";
  reset(): void;
}

type AccessorKeys<T> = {
  [K in keyof T & string]: K extends `get${string}` ? K : never;
}[keyof T & string];

type State<T> = {
  [K in AccessorKeys<T> as K extends `get${infer R}` ? Uncapitalize<R> : never]:
    T[K] extends () => infer V ? V : never;
};

const state: State<Store> = { userId: 42, theme: "dark" };
console.log(state.userId, state.theme);
console.log(Object.keys(state).sort());

Discussion

  • Be the first to comment on this lesson.