reactjsnext.jstailwind-cssshadcnui

How to use dynamic calculated tailwind classes in Shadcn UI with react


I am using NextJS with Shadcn. I am trying to display a command component that contains a dynamic number of items from a database.

This is a snippet of the line that is not working. Hard coding numbers works fine, but using variables has no effect on the layout.

 <CommandList className={`min-h-[calc(${favProjects.length * 50}px+${recentProjects.length * 50}px)]`}>

Here is the full code for context.

import {
    CalendarIcon,
    EnvelopeClosedIcon,
    FaceIcon,
    GearIcon,
    PersonIcon,
    RocketIcon,
} from "@radix-ui/react-icons"

import {
    Command,
    CommandEmpty,
    CommandGroup,
    CommandInput,
    CommandItem,
    CommandList,
    CommandSeparator,
    CommandShortcut,
} from "@/components/ui/command"

export function TopNavMenuProjects() {
    const favProjects = [
        {
            id: 1,
            name: "Project 1",
        },
        {
            id: 2,
            name: "Project 2",
        },
        {
            id: 3,
            name: "Project 3",
        },
    ]

    const recentProjects = [
        {
            id: 4,
            name: "Project 4",
        },
        {
            id: 5,
            name: "Project 5",
        },
        {
            id: 6,
            name: "Project 6",
        },
    ]
    return (
        <Command className="rounded-lg border shadow-md h-full">
            <CommandInput placeholder="Search ..." />
            <CommandList className={`min-h-[calc(${favProjects.length * 50}px+${recentProjects.length * 50}px)]`}>
                <CommandEmpty>No results found.</CommandEmpty>
                <CommandGroup heading={`Favourites (${favProjects.length})`}>
                    <CommandItem>
                        {/* <CalendarIcon className="mr-2 h-4 w-4" /> */}
                        <span>Project 1</span>
                    </CommandItem>
                    <CommandItem>
                        {/* <FaceIcon className="mr-2 h-4 w-4" /> */}
                        <span>Project 2</span>
                    </CommandItem>
                    <CommandItem>
                        {/* <RocketIcon className="mr-2 h-4 w-4" /> */}
                        <span>Project 3</span>
                    </CommandItem>
                </CommandGroup>
                <CommandSeparator />
                <CommandGroup heading="Recent">
                    <CommandItem>
                        {/* <PersonIcon className="mr-2 h-4 w-4" /> */}
                        <span>Project 4</span>
                        {/* <CommandShortcut>⌘P</CommandShortcut> */}
                    </CommandItem>
                    <CommandItem>
                        {/* <EnvelopeClosedIcon className="mr-2 h-4 w-4" /> */}
                        <span>Project 5</span>
                        {/* <CommandShortcut>⌘B</CommandShortcut> */}
                    </CommandItem>
                    <CommandItem>
                        {/* <GearIcon className="mr-2 h-4 w-4" /> */}
                        <span>Project 6</span>
                        {/* <CommandShortcut>⌘S</CommandShortcut> */}
                    </CommandItem>
                </CommandGroup>
                <CommandGroup heading="More Projects">
                    <CommandItem>
                        {/* <PersonIcon className="mr-2 h-4 w-4" /> */}
                        <span>Project Dashboard</span>
                        {/* <CommandShortcut>⌘P</CommandShortcut> */}
                    </CommandItem>
                </CommandGroup>
            </CommandList>
        </Command>
    )
}


I doubt the issue is specific to shadcn, but I am new to react, so don't want to rule anything out.


Solution

  • The reason it doesn't work is because tailwind only includes the classes it recognized when scanning your code, so dynamically generated classes are not included.

    From the tailwind docs:

    Don’t construct class names dynamically:

    // BAD
    <div class="text-{{ error ? 'red' : 'green' }}-600"></div>
    

    Instead, make sure any class names you’re using exist in full

    // GOOD
    <div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>
    

    The cn utility function

    If you've installed shadcnui following the installation guide on the site, your project will have a /lib/utils.ts file. Look here. It exports a cn() function, that may help applying whole tailwind styles based on a variable.

    For example:

    <Link
        className={cn(
            "text-sm hover:text-primary",
            someVariableClass
            route.active ? "text-black" : "text-muted-foreground",)
        }
    >
        Some label here.
    </Link>
    

    Default className would be this string "text-sm hover:text-primary". If the route.active is true, then adds "text-black" else "text-muted-foreground".

    Note that I'm using complete styles. If you're able to use the cn() utility function to achieve your results, go for it.

    The style prop

    If you truly want dynamic styles on runtime, then you can use the style prop to dynamically update the inline props passed to your component. Unlike tailwind which creates a stylesheet, the style prop passes inline styles which take immediate effect on runtime.