Challenge

The Block, Element, Modifier methodology (BEM) is a popular naming convention for classes in CSS.

For example, the block component would be represented as btn, element that depends upon the block would be represented as btn__price, modifier that changes the style of the block would be represented as btn--big or btn__price--warning.

Implement BEM<B, E, M> which generates a string union from these three parameters. Where B is a string literal, E and M are string arrays (can be empty).

Solution

In this challenge, we are asked to craft a specific string following the rules. There are 3 rules we must follow: Block, Element and Modifier. In order to simplify the overall look of the solution, I offer to split them into three separate types.

We start with the first one - Block:

type Block<B extends string> = any;

This one is pretty simple because all we need to do here is just to return a template literal type comprising an input type parameter:

type Block<B extends string> = `${B}`;

The next one is Element. It is not a template literal type as it was with Block, because we have a case when the array of elements is empty. So we need to check if the array is not empty and if so, construct a string. Knowing that an empty array returns never type, when accessed as T[number], we can check it with a conditional type:

type Element<E extends string[]> = E[number] extends never ? never : never;

If the array with elements is empty, we just return an empty literal type (no need to have a string with prefix __):

type Element<E extends string[]> = E[number] extends never ? `` : never;

Once we know an array is not empty, we need to add a prefix __ and then combine those elements in a template literal type:

type Element<E extends string[]> = E[number] extends never
  ? ``
  : `__${E[number]}`;

The same logic we apply to the last one - Modifier. In case the array with modifiers is empty - return empty literal type. Otherwise, return a prefix with the union of modifiers:

type Modifier<M extends string[]> = M[number] extends never
  ? ``
  : `--${M[number]}`;

What’s left is to combine those 3 types in our initial type:

type BEM<
  B extends string,
  E extends string[],
  M extends string[],
> = `${Block<B>}${Element<E>}${Modifier<M>}`;

The full solution, including all 4 types, looks like that:

type Block<B extends string> = `${B}`;
type Element<E extends string[]> = E[number] extends never
  ? ``
  : `__${E[number]}`;
type Modifier<M extends string[]> = M[number] extends never
  ? ``
  : `--${M[number]}`;
type BEM<
  B extends string,
  E extends string[],
  M extends string[],
> = `${Block<B>}${Element<E>}${Modifier<M>}`;

References