Skip to Content
Course content

192: Uppercase, Lowercase, Capitalize, Uncapitalize

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've likely run into a situation where you have a set of string constants—maybe API endpoints, event names, or CSS property keys—and you need a derived version of those strings in a different case. I used to handle this by just writing out a second union type. It seems harmless at first, but it's a maintenance trap waiting to spring.

The manual maintenance trap

Let's say you're building a small event system. You have a list of events, and you want to define a corresponding set of handler names that are simply the capitalized versions of those events. The naive approach looks like this:

type UserEvent = 'login' | 'logout' | 'updateProfile';
type UserEventHandler = 'Login' | 'Logout' | 'UpdateProfile';

function handleEvent(event: UserEvent, handler: UserEventHandler) {
  // ... logic here
}

On the surface, this works. But here is where it breaks: the moment your team decides to add a 'resetPassword' event, you have to remember to update UserEventHandler as well. If you forget, the compiler won't tell you that the two types are out of sync; it'll just tell you that 'ResetPassword' isn't allowed in the handler type. I've spent way too many hours debugging "undefined is not a function" errors because someone added a string to one union and forgot the other.

Letting the compiler do the heavy lifting

TypeScript provides intrinsic string manipulation types—Uppercase, Lowercase, Capitalize, and Uncapitalize—specifically to kill this pattern. These aren't functions that run at runtime; they are type-level transformations. Instead of duplicating your work, you can derive the handler type directly from the event type.

type UserEvent = 'login' | 'logout' | 'updateProfile';

// Now, if UserEvent grows, UserEventHandler grows with it automatically.
type UserEventHandler = Capitalize<UserEvent>; 
// Result: 'Login' | 'Logout' | 'UpdateProfile'

This is a huge win for type safety. You've created a single source of truth. If you change 'login' to 'signIn', the handler type instantly updates to 'SignIn' without you touching a second line of code.

Choosing the right transformation

Depending on what you're building, you'll need different tools. Uppercase<T> and Lowercase<T> are blunt instruments—they hit every character. Capitalize<T> and Uncapitalize<T> only touch the first character. I find these particularly useful when working with naming conventions, like converting a PascalCase class name into a camelCase instance property.

type ComponentName = 'UserDashboard' | 'SettingsPanel';
type ComponentInstance = Uncapitalize<ComponentName>;
// Result: 'userDashboard' | 'settingsPanel'

One thing to keep in mind: these only work on string literal types. If you try to pass a generic string type into Capitalize, you just get string back. That's because TypeScript can't predict the casing of a value that hasn't been defined yet. These tools are for when you know the exact set of possible strings you're dealing with.




📋 Practical Task

Refactoring the CSS Property Mapper

You are working on a CSS-in-JS library. Currently, the library defines a set of supported CSS properties in kebab-case, and then manually defines a second type for the corresponding camelCase properties used in JavaScript. This is causing bugs whenever a new property is added.

Your Task: Refactor the following code to remove the manual JsCssProperty union. Use the Uncapitalize intrinsic type to derive JsCssProperty from CssProperty. Then, add 'border-radius' to the CssProperty union and verify that the derived type updates automatically.

type CssProperty = 'BackgroundColor' | 'FontSize' | 'MarginTop';

// TODO: Refactor this line to use Uncapitalize<CssProperty>
type JsCssProperty = 'backgroundColor' | 'fontSize' | 'marginTop';

function setStyle(prop: JsCssProperty, value: string) {
  console.log(`Setting ${prop} to ${value}`);
}

// This should work automatically after your refactor
setStyle('borderRadius', '10px'); 
Rating
0 0

There are no comments for now.

to be the first to leave a comment.