Jevon Cochran
Jevon Cochran

Reputation: 1774

How to add multiple classNames to nextjs elements

I have an unordered list element that looks like this:

     <ul className={styles["projects-pd-subdetails-list"]}>
        {detail.subdetails.map((sub) => (
          <li
             className={styles["projects-pd-text projects-pd-subdetail"]}
          >
            {sub}
          </li>
        ))}
     </ul>

With a normal React element, I would be able to apply the multiple classes for the li element like this:

<li className="projects-pd-text projects-pd-subdetail">{sub}</li>

However, having a space like I do in nextjs means the styles are just getting ignored. How can I fix this problem and properly account for two classNames for my li element here?

Upvotes: 45

Views: 76560

Answers (12)

krishnaacharyaa
krishnaacharyaa

Reputation: 25120

In Nextjs 13.4+ you can use default supported cn which internally uses clsx


layout.tsx


import { cn } from "@/lib/utils";
export default function Component(){
   return <div className={cn("flex font-sans","inter.className","h-full")}/>
}

By default generated file: @ lib/utils.ts

import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
 
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Upvotes: 2

Robin
Robin

Reputation: 5427

You can use multiple className like this

<li className={`${styles.projects-pd-text} ${styles.projects-pd-subdetail}`}>
   {sub}
</li>

But there is a problem. It may throws an error(I guess, not sure). You may use camelCase in your css className.

<li className={`${styles.projectsPdText} ${styles.projectsPdSubdetail}`}>
   {sub}
</li>

or, if you don't want to camelCase

<li className={`${styles["projects-pd-text"]} ${styles["projects-pd-subdetail"]}`}>
       {sub}
</li>

Let me know if it works.

And another convenient way of using multiple classes using clsx library. The good part of clsx is - you can handle conditional class name also.

// Strings (variadic)
clsx('foo', true && 'bar', 'baz');
//=> 'foo bar baz'

// Objects
clsx({ foo:true, bar:false, baz:isTrue() });
//=> 'foo baz'

// Objects (variadic)
clsx({ foo:true }, { bar:false }, null, { '--foobar':'hello' });
//=> 'foo --foobar'

// Arrays
clsx(['foo', 0, false, 'bar']);
//=> 'foo bar'

// Arrays (variadic)
clsx(['foo'], ['', 0, false, 'bar'], [['baz', [['hello'], 'there']]]);
//=> 'foo bar baz hello there'

// Kitchen sink (with nesting)
clsx('foo', [1 && 'bar', { baz:false, bat:null }, ['hello', ['world']]], 'cya');
//=> 'foo bar hello world cya'

Upvotes: 66

vishu
vishu

Reputation: 601

A simple array join should suffice.

["class1", "class2", "class3"].join(" ")

result: "class1 class2 class3"

<li className={[styles.projects_pd_text, styles.projects_pd_subdetail].join(" ")}>
   {sub}
</li>

Or save it as a variable for re-using :>

const listClasses = [styles.projects_pd_text, styles.projects_pd_subdetail]

// somewhere in the code
<li className={[...listClasses, styles.projects_pd_outline].join(" ")}>
   {sub}
</li>

Upvotes: 24

tr05t
tr05t

Reputation: 63

The array join() is the cleanest solution, as @rain mentioned here.

You can also use global css class names.

<span className={[styles.grid, "disabled"].join(" ")}>content</span>

Upvotes: 3

aminben slimen
aminben slimen

Reputation: 81

a Function would be much cleaner

const convertToNextClassName = (className) =>  className.split(' ').map(c => styles[c]).join(' ')

then in the jsx you can directly call it

<div className={convertToNextClassName("firstClass secondClass")}></div>

for conditional i suggest using classnames from npm

Upvotes: 0

Shawn Lee
Shawn Lee

Reputation: 59

 <div className={`${GloStyles.flexRow} ${MyStyles.gap_10rem}`}> Hello </div>

Explanation

  • className accept 'String' only
  • use 'String template' solved, it is just like a normally react app className, nothing fancy

enter image description here enter image description here

Upvotes: 0

Mausam Kumar Giri
Mausam Kumar Giri

Reputation: 126

It worked for me.

<div className={styles.side +" "+ styles.side2}>side2</div>

Thanks to CodenNerd

Upvotes: 1

jason-warner
jason-warner

Reputation: 111

If you console log your css or scss module class (ex. ImportedStyleModule.someClassName) you'll see it's just a string that has an auto generated UID concatenated.

Ergo, it's just a string so you can use a number of ways to join them like so:

//ts         
const mergeStyles = (styleArray: string[]) => (styleArray.map((style: string) => `${style}`).join(" "));        
                        
//js
const mergeStyles = (styleArray) => (styleArray.map((style) => `${style}`).join(" "));
                        
//example
return (
    <span
        onClick={() => setIsClicked(!isClicked)}
        className={`${!!isClicked 
            ? mergeStyles([NavbarStyle.navButton, NavbarStyle.navButtonOpen])
            : NavbarStyle.navButton}`}
    >
        <Image src='/image-logo.svg' alt='logo' width='80px' height='40px' />
    </span>
);

Upvotes: 0

CodenNerd
CodenNerd

Reputation: 47

Because it might be tedious to always write styles.className for every class you need to add to an element, you can create a utility function that makes things look neater.

For example, in my case, I created this function:

export const classes = (styles: any, classes: string) => {
    const list = classes.split(' ');

    classes = '';
    for (const className of list) {
        classes += `${styles[className] }`
    }
    return classes;
} 

in a util file.

And on elements, I can do this

<div className={classes( styles, 'hero-container text-success text-bold italicize another-class yet-another-class ')}>
     Lorem ipsum dolor
</div>

A PLUS:

One other issue around classnames I encountered getting started with NextJS on VSCode was getting emmet tag generation to list the classnames the NextJS way when I type css class selector.

    "Complete NextJS Classname Emmet": {
        "scope": "javascript,typescript,typescriptreact,javascriptreact",
        "prefix": ["."],
        "body": [
            "<div className={classes( styles, '$1')}>",
            "\t$2",
            "</div>"
        ],
        "description": "Lean autocomplete emmet in nextjs css modules classname"
    }

I added this to my VSCode > Preferences > User Snippets > typescriptreact.json

These made working with classnames in NextJS easier for me - especially on VSCode.

Upvotes: 1

Foysal imran
Foysal imran

Reputation: 143

This is how it works for me in my style component

<ul>
<li className={router.pathname == "/" && styles.active}><Link href="/">Home</Link></li>
<li className={router.pathname == "/about" && styles.active}><Link href="/about">About</Link></li>
<li className={router.pathname == "/contact" && styles.active}><Link href="/contact">Contact</Link></li>
<li><Link href="/404">404</Link></li>
</ul>

Upvotes: -1

Jacob Smit
Jacob Smit

Reputation: 2379

As stated in my original comment I have not worked with Next.js.

It appears as though styles is a map of some kind i.e.:

const styles = {
    "projects-pd-subdetails-list": "Class Name A",
    "projects-pd-text": "Class Name B",
    "projects-pd-subdetail": "Class Name C"
}

This means that by using a line similar to styles["projects-pd-text projects-pd-subdetail"] you are attempting to retrieve the value for the key "projects-pd-text projects-pd-subdetail" which does not exist.

I would suggest retrieving the values individually from the map and then joining them together with your choice of string concatenation.

className={styles["projects-pd-subdetail"] + " " + styles["projects-pd-text"]}

// OR

className={`${styles["projects-pd-subdetail"]} ${styles["projects-pd-text"]}`}

Upvotes: 18

Prathamesh sawant
Prathamesh sawant

Reputation: 51

clsx is generally used to conditionally apply a given className.

https://www.npmjs.com/package/clsx

Upvotes: 3

Related Questions