Autocomplete Literal Unions

Pro Members Only

This lesson is available if you have an active subscription.

Alternatively, some member might be able to gift it to you.

If you already have a subscription, you can sign in.

Autocomplete Literal Unions

Subscription Required

You must have an active subscription to access this content.
If you already have a subscription, you can sign in.

Example Without Trick

type Padding = 'small' | 'normal' | 'large' | string;

function getPadding(padding: Padding): string {
if (padding === 'small') return '12px';
if (padding === 'normal') return '16px';
if (padding === 'large') return '24px';
return padding;
}

let padding: Padding;
padding = ''; // No Autocomplete

Example With Autocomplete Trick

Use the & {} to trigger the compiler to preserve autocomplete. Try the autocomplete on padding values

type Padding = 'small' | 'normal' | 'large' | (string & {});

function getPadding(padding: Padding): string {
if (padding === 'small') return '12px';
if (padding === 'normal') return '16px';
if (padding === 'large') return '24px';
return padding;
}

let padding: Padding;
padding = 'small'; // 12px
padding = '8px'; // 8px

Is it safe to do this?

Yes 💯%. The TypeScript team officially supports this (& {}) feature 🫰🏻

javascript
typescript
react
playwright

Enjoy free content straight from your inbox 💌

No spam, unsubscribe at any time.

Transcript

00:00

When you are working with the design system component library, it's not uncommon to have a property that is limited to a number of well-defined string literals or design tokens, as well as open to any string value to allow the user to provide their own custom value. For example, in pixels. However, the simple annotation that you might use for such a property within TypeScript will completely mess up auto complete, and that is what people demo and fix with the more advanced annotation. So let's go. Here's the type that represents the kind of problem that we are trying to solve. We have a type called padding that takes well-defined design tokens, small, normal,

00:34

and large, as well as allows the user to provide any arbitrary padding value. Now, before this padding ever gets s rendered to the screen, we would pass it through this utility function called get padding, mapping the design tokens to their values and leaving untouched any other value that might be passed in. Now let's look at the developer experience. When someone tries to use a value of type padding, they can provide any of these design tokens as well as any other arbitrary string. Now, what you might not realize is that TypeScript has already figured out the fact that all of these string littles are a subset of the string type, and therefore it has collapsed padding into a

01:09

single string type. This means that whenever we try to assign a value to something of padding type, we do not get any autocomplete for these string littles. If, however, we were to remove the string type from this union and then try to assign a value to something of type padding, we get a nice autocomplete for the three littles that are present over here. However, removing string from the union means that strings are no longer valid petting values, and we get an error when we try to assign eight pixels. Now, the thing that we want to achieve is to still allow arbitrary strings,

01:42

but provide nice auto complete for the well-defined design tokens. Now, the workaround that we are going to use is not based on a language specification, but rather on the internals of the types of compiler and how it skips this aggressive reduction of a primitive type With this literal union, for example, the union of string, a primitive type with a union of literal strings. Instead of doing a union with a simple string, we do a union with a string combined with something else like an empty object with a simple intersection. With this change in place, the literals as well

02:14

as the string are preserved in the final type of padding, and whenever we try to assign a value of type padding, we get nice auto complete for these literal members. Additionally, padding takes all strings. For example, it takes this eight pixel of value, and it can also be assigned to a string. For example, when we return the final padding value from the get padding function, TypeScript is happy to treat it as a primitive string.