back to series Implement a classnames function Apr 01, 2024
classNames
is popular Javascript package used to conditionally apply and join CSS classnames
together.
Variation: Basic implementation We assume the possible values are strings, boolean, arrays and objects. We must also remove duplicates in classnames.
function classNames ( ... classes ) {
const result = new Set ();
function classNamesImpl ( ... classesArray ) {
classesArray. forEach (( cls ) => {
// if not truthy we don't want to include them
// for string and numbers
if ( typeof cls === 'string' || typeof cls === 'number' ) {
result. add (cls. toString ());
if (Array. isArray (cls)) {
cls. forEach (( classValue ) => {
classNamesImpl (classValue);
// to avoid iterating over inherited keys
if (Object. hasOwn (cls, key)) {
// we only want truthy values for object
// if truthy value and not nested we simply want the key
if ( typeof value !== 'object' ) {
// recursively get the keys from the nested object
classNamesImpl ( ... classes);
return Array. from ( new Set (result)). join ( ' ' );
// yields : "red yellow 1"
console. log ( classNames ( 'red' , 'yellow' , 'yellow' , null , false , 1 , undefined ));
// yields : "red yellow purple magenta"
classNames ([ 'red' , 'yellow' ], {
Variation: Toggle classes In the above implementation, we only considered cases to add classes (as object keys) whemever they had a truthy value. In this implementation we can remove classes from the final result when same class with a falsy value appears on the object.
An example to demonstrate the idea:
import classNames from './src/classNames.js' ;
const output = classNames ( 'foo' , 'bar' , { foo: false });
console. log ( 'output' , output); // yields "bar" and not "foo bar"
Most of the implementation for this variation remains exactly same as above, so I will highlight only the new changes which needs to be made to accomodate the new case.
function classNames ( ... classes ) {
const result = new Set ();
function classNamesImpl ( ... classesArray ) {
classesArray. forEach (( cls ) => {
// if not truthy we don't want to include them
// for string and numbers
if ( typeof cls === 'string' || typeof cls === 'number' ) {
result. add (cls. toString ());
if (Array. isArray (cls)) {
cls. forEach (( classValue ) => {
classNamesImpl (classValue);
// to avoid iterating over inherited keys
if (Object. hasOwn (cls, key)) {
// add to result when truthy
// if truthy value and not nested we simply want the key
if ( typeof value !== 'object' ) {
// recursively get the keys from the nested object
// remove if already consider when falsy
classNamesImpl ( ... classes);
return Array. from ( new Set (result)). join ( ' ' );
// yields : "red yellow 1"
console. log ( classNames ( 'red' , 'yellow' , 'yellow' , null , false , 1 , undefined ));
// yields : "red purple magenta"
classNames ([ 'red' , 'yellow' ], {
I strongly encourage you to explore and tackle additional questions in my Recursion Questions for Frontend Interviews blog series.
By doing so, you can enhance your understanding and proficiency with recursion, preparing you to excel in your upcoming frontend interviews.
Wishing you best. Happy Interviewing 🫡.