Applying an effect to multiple pseudo classes at once in tailwind
Asked Answered
C

3

7

I am looking for a good shorthand to apply the same effect to multiple pseudo classes in tailwind.

For example, I may want to apply a blue background to both :hover and :focus states on a div.

Currently I'd have to write the following:

<div className="hover:bg-blue focus:bg-blue>Text<div>

OR, i could use apply to build out a custom class like this:

.hover-focus-bg-blue {
  @apply hover:bg-blue focus:bg-blue
}

But neither of these are great options when i have to apply complex states (in my current project I need to cover 11 states on one element (rest/hover/active/focus/focus-visible/focus-visible && hover etc).

The apply method only saves code if there are multiple uses of it.

What I would like to see is something like:

<div className="[hover, focus]:bg-blue">Text</div>

Does anyone know of some syntax like that? Can't find it anywhere.

Cheryle answered 15/11, 2022 at 12:10 Comment(2)
I know it doesn't work with pseudo-selectors, and I'm pretty sure the same logic applies here. Read here: #74167188Valance
Thats great - thanks, stick that in as an answer to the questions and i'll mark it correct. Cheers!Cheryle
V
6

This is a question that is asked frequently. Mostly it's about grouping classes into one grouped syntax (for example, hover:(bg-red-500 border-2).

This is the answer I received after searching for a solution to this question. In short, this functionality doesn't exist for now. (Edit: It's possible to achieve now with some different tooling, see the end of the answer.) Even though this answer refers to the grouping of utilities into one selector, the same logic should apply to the grouping of pseudo-selectors:

Wongjn's answer

The Tailwind maintainers did look at this feature at one point but ultimately decided to put it on hold for now. See this Twitter thread: https://twitter.com/adamwathan/status/1461519820411789314

So, as you can see from the answer and the Twitter feed, it's currently on hold. In the Twitter thread, the developers were testing this feature regarding performance, and it looks like this feature, at its current state duplicates CSS compared to using individual utilities:

So we did a test where we converted every single Tailwind UI template (over 500 files) to use the grouped syntax to see how much bandwidth grouping would save you when serving HTML.

This makes sense when you think about it, because using the grouped syntax (like focus:(font-bold,underline)) leads to fewer repeated symbols in the document, because there are now more unique class names.

Using the non-grouped syntax, every instance of focus:font-bold can be compressed out and replaced with a short placeholder (say %). Using grouped syntax, focus:font-bold and focus:(font-bold,underline) can't be compressed out, because they are no longer the same. Plot twist: After compression, the files are actually bigger, not smaller!

Bottom line:

So the takeaway here is that although the grouped syntax looks like less code when you're authoring it, it actually creates both a bigger CSS file and a bigger HTML file in production, making it a very black and white performance anti-pattern.

It's nicer to write though, and the performance cost isn't a huge one, so still a chance we develop it further just for the developer experience for the people who like it. But admittedly hesitant to encourage anything that's bad for performance.

Update

The disadvantage about the variant group resulting in larger CSS files only holds true if the CSS class in your HTML/JSX are simply left as is (e.g. focus:(font-bold,underline)). But if it was just a syntactic sugar shorthand that is unfolded into the full variant (focus:font-bold focus: underline) at build time, then for the bundle size it's the same as manually typing it out, so no disadvantage, while having the advantage of faster coding and more readable source code.

As TailwindCSS is limited to purely generating CSS files, it cannot do such a transformation. However, there are new tools available such as UnoCSS that integrate with your build system (such as Vite or Webpack) to also inspect your HTML/JSX and unfold the syntactic sugar. Check out this playground to try it out.

Valance answered 15/11, 2022 at 15:32 Comment(1)
I think the question is phrased slightly differently from what you answered. The person is asking how to add multiple pseudo selectors to a single style, not the other way around. Either way, I don't think the feature exists atm.Cheatham
U
1

Edit: These functions are useless, because your site will be up before these functions even run. Leaving them here in case may be you can use it if you don't use vite.

I recently provided an answer in another question for grouping classes here but your question is a bit different so here's an improved version to the previous function:

const pseudoJoin = (selectors, str) => {
  let result = "";
  selectors.forEach(selector=> result+=selector+":"+str.split(" ").join(" "+selector+":")+" ")
  return result;
}

Now you can call it anywhere like:

<div className=`${pseudoJoin(['hover','focus'],"classes you want on hover & focus")} some more classes here ${pseudoJoin(['focus'],"classes when focused")}`>Hello World!</div>

Or when you are using classnames framework:

<div className={ classnames(
   pseudoJoin(['hover','focused'], "classes you want on hover & focused"),
   "Other classes here",
   pseudoJoin(['focused'], "classes when focused")
)}>Hello World!</div>

For making it shorter even further, you can replace pseudoJoin with a shorter name because I couldn't think of a better name.

Upstage answered 15/1, 2023 at 7:31 Comment(0)
U
1

My previous answer was a lightweight, fast function, but this one doesn't focus on perfomance but rather accounts for ease.

const pseudoJoin = (str) => {
  let result= [];
  let storedvar;
  str=str.split(" ");

  str.forEach(function(s,i){
      if((/\:\(/).test(s)) storedvar=i;
      if(!storedvar) result.push(s);
      if(s.endsWith(")")){
          result.push(str.slice(storedvar,i+1).join(" "))
          storedvar=null;
      }
  })
  
  str=[]
  result.forEach(function(s,i){
    if((/\w\:/).test(s)){
      storedvar = s.split(/\:(.*)/s);
      
      if(s.endsWith(")")){
        storedvar[1].slice(1,-1).split(" ").forEach(function(t){
          storedvar[0].split("+").forEach(function(x){str.push(x+":"+t)})
        })
      } else {
        storedvar[0].split("+").forEach(function(x){
          str.push(x+":"+storedvar[1])
        })
      }
    } else {
      str.push(s)
    }
  })
  return str.join(" ");
}

Here's example of how to use the new function:

pseudoJoin("hover:text-black hover+focus+active:(bg-white margin-[3.2rem] underline) before+after:content-[Hello_\+_I_am_groot] sm:hidden"));


Upstage answered 17/1, 2023 at 15:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.