Wanna see something cool? Check out Angular Spotify 🎧

Classnames implementation in TypeScript

Still the same conversation with my friend who recently went for an interview. After the Python indentation task, the next challenge was another familiar one for front end engineers — implement the classnames utility, or the newer clsx used in React apps.

If you have used React, you have probably written code like this:

<div className={classNames('btn', isActive && 'active')} />

This utility helps combine class names conditionally and neatly, now let’s see how simple it is to implement it.

Requirement

classnames is a commonly-used utility in modern front end applications to conditionally join CSS class names together. If you’ve written React applications, you likely have used a similar library.

Implement the classnames function.

Examples

classNames('foo', 'bar'); // 'foo bar'
classNames('foo', { bar: true }); // 'foo bar'
classNames({ 'foo-bar': true }); // 'foo-bar'
classNames({ 'foo-bar': false }); // ''
classNames({ foo: true }, { bar: true }); // 'foo bar'
classNames({ foo: true, bar: true }); // 'foo bar'
classNames({ foo: true, bar: false, qux: true }); // 'foo qux'

Arrays will be recursively flattened as per the rules above.

classNames('a', ['b', { c: true, d: false }]); // 'a b c'

Values can be mixed.

classNames(
  'foo',
  {
    bar: true,
    duck: false,
  },
  'baz',
  { quux: true },
); // 'foo bar baz quux'

Falsey values are ignored.

classNames(null, false, 'bar', undefined, { baz: null }, ''); // 'bar'

In addition, the returned string should not have any leading or trailing whitespace.

Thought process

I have solved similar problems before, like converting snake_case to camelCase, which taught me to check the type first — if the value is a string, object, or array — and then deal with each case properly.

This problem is about combining class names based on rules. The tricky part is dealing with mixed types, recursive arrays, and ignoring falsey values like null, undefined, false, and ''.

I broke the problem into smaller pieces:

1. If the input is a string

Use the string as-is (after trimming).

if (typeof input === 'string') {
  return input.trim();
}

2. If the input is a number

Just convert it to string.

if (typeof input === 'number') {
  return input.toString();
}

3. If the input is an array

Recursively flatten each value inside the array.

if (Array.isArray(input)) {
  return input.map(flattenClass).join(' ');
}

4. If the input is an object

Pick only the keys that have truthy values.

if (typeof input === 'object') {
  return Object.entries(input)
    .filter(([_, value]) => Boolean(value))
    .map(([key]) => key)
    .join(' ');
}

Ignore everything else (null, false, undefined, empty string). After applying all these steps, collect the results and join with a space.


Final implementation

export type ClassValue =
  | string
  | number
  | boolean
  | null
  | undefined
  | ClassDictionary
  | ClassArray;

export type ClassDictionary = Record<string, any>;
export type ClassArray = Array<ClassValue>;

export default function classNames(...args: ClassValue[]): string {
  return args.map(flattenClass).filter(Boolean).join(' ').trim();
}

function flattenClass(input: ClassValue): string {
  if (!input) return '';
  if (typeof input === 'string') return input.trim();
  if (typeof input === 'number') return input.toString();
  if (Array.isArray(input)) return input.map(flattenClass).join(' ');
  if (typeof input === 'object') {
    return Object.entries(input)
      .filter(([_, value]) => Boolean(value))
      .map(([key]) => key)
      .join(' ');
  }
  return '';
}

Results

You can practice this question here

classnames

Published 31 May 2025

    Read more

     — Validating Python Indentation in TypeScript
     — React Aria Components - Slider with filled background
     — Learn Angular Signals by implementing your own
     — Cursor: Customize Your Sidebar Like VS Code
     — bundle install: Could not find MIME type database in the following locations