title: How to get helpful intellisense when using a type alias for a string union type as a property of an object or interface?
body: <p>At work, I'm involved in maintaining our React component library, which we're very slowly in the process of converting to TypeScript. (But my question is about Typescript more generally and not specific to React - that's just how the question has arisen and how I imagine it might arise for many others!)</p>
  <p>Like many such component libraries, we often use a union of string literals as the type for some props, to ensure they always have one of a small number of approved values - a typical example might be <code>type Size = &quot;small&quot; | &quot;medium&quot; | &quot;large&quot;;</code>. And while we don't always use a type alias for them as shown there, it is frequent enough that we do, as the type may need to be referred to in a few different places, and it's nice (particularly with larger unions than this) to not have to type out the same thing all the time, as well as knowing that if the design team ever want us to add a new <code>extra-large</code> size or whatever, the type at least will only have to be updated in one place.</p>
  <p>This works fine of course, but we've discovered that in terms of intellisense it leaves quite a lot to be desired. We want our consumers, who likely don't know all the valid values off my heart, to be told what these are by their IDE when rendering the component. But by doing this in the most obvious way - ie like this:</p>
  <pre><code>type Size = &quot;small&quot; | &quot;medium&quot; | &quot;large&quot;;
  
  interface Props {
    size: Size;
  }
  </code></pre>
  <p>then when consuming the props in any form, IDEs such as VSCode on hovering over the <code>size</code> prop will simply display the rather unhelpful <code>Size</code> as the prop's type, rather than the explicit union of 3 strings which would be far more helpful.</p>
  <p>(Note that although the above is using an interface rather than a type alias, which is the way we've decided to go after some debate, the same issue is present when using <code>type</code> for
  the props type.)</p>
  <p>This seems like it should be a common question, but many searches on google and Stack Overflow have failed to turn anything up that's specific to simple unions of strings.</p>
  <p>There <em>are</em> discussions about ways to get TS to &quot;expand&quot; or &quot;simplify&quot; complex types - like <a href="https://stackoverflow.com/questions/57683303/how-can-i-see-the-full-expanded-contract-of-a-typescript-type">this Stack Overflow question</a> and its great answers, or <a href="https://effectivetypescript.com/2022/02/25/gentips-4-display/" rel="nofollow noreferrer">this article</a>, which basically show the same solution although they differ in details. But these seem to be aimed at - and certainly work on - object types specifically. Sadly, no matter which of these transformations is applied to the type of the <code>size</code> prop in the above example, TypeScript still stubbornly shows the unhelpful <code>Size</code> as the prop's type when actually consuming it. (For those for whom this makes sense - Haskell is among my favourite languages - I would phrase this more succinctly as: these solutions appear to work on product types but not on sum types.)</p>
  <p>This is demonstrated in <a href="https://www.typescriptlang.org/play?#code/C4TwDgpgBAogHmAhgOwCYB4AqA+KBeKTKCOYCNAZygEtkAzCAJygHkoB+KAbygG0BpGsigBrCCAD2dVgF0AXKwEyoAXygLkEAG5MA3AFgAUEdCQoAOQkswmCAFswAG0RksuAkRJlKUCsEa0AOYcUAAGACRcmCqRAESxKqHqhAaGptC2Ds5kAOrUwAAWmADuEgAKiIyIdhRYADRQAKrEpOSoVH4ByIHuhC3e7b7+QSERUTFcjYnJmKkm4NAAytQAXtAEsRR2iI6OsVAAPlCxdhCo1ACudvtHsc6MgRCxc4a0ZIx0iADG0GWMEmAqFwjFBQb5VhAAIwKZZrVJg8FrABMCngSDQ6FhEGw8LBFAhAGYFJZrJknC4IJiITiQXiIQAWBRk7IQPKFErlSrVWpYhrxGmGBHICTAACKF3yECxTPs5Ny+SKpQqVRqVLWfLgsQFKheXwkyD8UEWElOAGETWB9eRgPgoAAKYGCulrSF1Wmg-HIt1Oj2E70Iz0Qen+sHCsUSshYowqBR-AEUACUCk6IzwuEdCMYEGAF0YwhOICgeocVuQwGe0dSQA" rel="nofollow noreferrer">this TS playground example</a> - specifically the <code>size2</code> prop. (This shows only one form of the <code>Expand</code> type, but I've tried every slight variation I've either found online or have come up with myself, with no success.)</p>
  <p>The others - <code>size3</code> and <code>size4</code> - are attempts at using <a href="https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html" rel="nofollow noreferrer">template literal types</a> based on the same &quot;trick&quot; that is behind the <code>Expand</code> example. I understand they're based on using a <a href="https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types" rel="nofollow noreferrer">conditional type to force distribution of the operation across a union</a> and then making sure the operation is essentially a no-op, so the actual type is still the same but after hopefully forcing TS to compute something across the union to output a &quot;new&quot;, &quot;plain&quot; union. Since the <code>Expand</code> type suggested above iterates across keys, fine for an object type with properties but unclear if it has any meaning for a union of string literals, it seemed using a template literal as the operation in this trick was the same idea but adapted to such a union of literal string types. And specifically, concatenating with an empty string is the only obvious way to make this keep the strings as they were.</p>
  <p>That is, I thought</p>
  <pre><code>type NoOpTemplate&lt;T&gt; = T extends string ? `${T}${&quot;&quot;}` : T;
  </code></pre>
  <p>might work. But as you will see from the playground link above (<code>size3</code> prop), it doesn't.</p>
  <p>I even hit upon the idea of using a generic type with <em>two</em> parameters:</p>
  <pre><code>type TemplateWithTwoParams&lt;T, U extends string&gt; = T extends string ? `${T}${U}` : T;
  </code></pre>
  <p>This &quot;works&quot; in a sense, because the prop defined as <code>notQuiteSize: TemplateWithTwoParams&lt;Size, &quot;x&quot;&gt;;</code> displays as an explicit union as desired: <code>&quot;smallx&quot; | &quot;mediumx&quot; | &quot;largex&quot;</code>. So surely supplying an empty string as the <code>U</code> parameter will do what we want here - keep the strings in the union the same, while forcing explicit display of the options?</p>
  <p>Well no, it doesn't, as you can see from the <code>size4</code> prop in the example! It seems the TS compiler is just too clever, as all I can assume is that it spots that in this case we're just concatenating with an empty string, which is a no-op, and therefore doesn't need to actually compute anything and thus outputs the same type <code>T</code> as it was given - even via the type alias.</p>
  <p>I'm out of ideas now, and surprised this doesn't seem to be a common problem with clever solutions like the above <code>Expand</code> that I can read about online. So I'm asking about it now! How can we force TS to display a union type as an explicit union, when used as part of an object or interface, while still keeping the convenience of using an alias for it?</p>