Search code examples
cssreactjstailwind-cssnext.js13radix-ui

Why applying TailwindCSS and custom CSS classes on RadixUi is not working?


I am using Radix UI with Tailwind CSS. When I apply Tailwind CSS classes or vanilla CSS classes on Radix UI components, the tailwind/vanilla CSS styles do not override the styles of Radix UI.

<TextField.Input
    className="[&&]:h-[57px] [&]:rounded-[8px] text3 [&&]:border-1 [&&]:border-solid 
    [&&]:border-second_black"
    placeholder="Enter your email"
/>

Should I have to add "[&&]:" to make that class a priority? Why is that and how can I have my CSS or Tailwind CSS to have higher precedence over Radix UI styles?


Solution

  • [&&] increases class specificity, but you shouldn't depend on it since you never know how high the specificity you would need to compete with the third party component's selectors. They are usually not consistent and can randomly overweight yours in any minor update. (Even if you do something like [&&&&&&&&&&&&&] it will be overweighted by a single new id selector.) It also bloats the final generated files' size, as another added drawback.

    In tailwind you make a utility important by adding ! to the beginning, so you should do:

    <TextField.Input
        className="!h-[57px] !rounded-[8px] !text-3 !border-solid !border-second_black"
        placeholder="Enter your email"
    />
    

    However, for RadixUi Textfield, it seems like even if you omit the use of TextField.Root, it will still be generated along with a child rt-TextFieldChrome div (covered by TextField.Input). Hence, border styles need to be applied on both Input and TextFieldChrome. Otherwise, after adding the border radius on Input, the covered div will appear like below: (I've made the border radius and color more significant for better demonstration)

    Not selecting both child

    You can use [&>*] to select both or specify both [&>input] and [&>.rt-TextFieldChrome] if you have other components wrapped under TextField.Root:

    <TextField.Root className="[&>*]:!rounded-[50px] [&>*]:!border-solid">
        <TextField.Input className="!h-[57px]" placeholder="Search the docs…"/>
    </TextField.Root>
    

    You can observe this type of pattern across many popular libraries, such as MUI, and this complexity is the main reason why, when customizing these third-party components, we generally prefer to use the properties provided by the library. However, in this case, the interface provided by RadixUI is not enough to meet your requirements.

    What is [&&] and why it works?

    The use of & in tailwind are for arbitrary variants, where & represents the selector being modified.

    First, lets take the document's example [&:nth-child(3)]:underline to explain how it works. Here & will be replaced by the current selector (the class name [&:nth-child(3)]:underline with a bunch of escape characters \ to avoid the preserved css keywords) and a dot in front, generating this:

    /* Selecting elements with class name "[&:nth-child(3)]:underline",
       but only when it’s the third child. */
    .\[\&\:nth-child\(3\)\]\:underline:nth-child(3) {
      text-decoration-style: underline /*this is the style from :underline*/
    }
    

    Now look at [&&]:h-[57px], each & will be replaced to a dot and the current selector (the class name [&&]:h-[57px]), so the generated selector will be .[&&]:h-[57px].[&&]:h-[57px] which selects all elements with class [&&]:h-[57px] and [&&]:h-[57px].

    While selecting the same class twice doesn't make a difference on what elements are selected, since now there are two class selectors included in this CSS declaration, its specificity of the class column increases from 1 to 2, making it more competitive for its css chosen to be applied.