Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Proposal] - Nested Group Hover #1192

Closed
Ratko-Solaja opened this issue Oct 27, 2019 · 25 comments
Closed

[Feature Proposal] - Nested Group Hover #1192

Ratko-Solaja opened this issue Oct 27, 2019 · 25 comments

Comments

@Ratko-Solaja
Copy link

Hi,

When creating nav dropdown, I usually just add group class to my <li> element, and then to my <ul> that is child of that <li> I add something like opacity-0 invisible backface-invisible group-hover:opacity-100 group-hover:visible group-hover:backface-visible. Which works fine, but then if my dropdown will have another dropdown, this won't work.

It would be awesome if we could have nested group hover. :)

@royvanv
Copy link

royvanv commented Dec 12, 2019

It is possible if you change the group-hover selector to a direct child selector and the right HTML structure.

.group:hover > .group-hover\:visible {
    visibility: visible;
}
<div class="group">
    <div>Parent</div>
    <div class="invisible group-hover:visible">
        <div class="group">
            <div>Child</div>
            <div class="invisible group-hover:visible">Grandchild</div>
        </div>
    </div>
</div>

But there must a better way to handle nested group hovers!

@tlgreg
Copy link

tlgreg commented Dec 12, 2019

Related conversation: tailwindlabs/discuss#337

@adamwathan
Copy link
Member

I would recommend solving this problem with a custom variant plugin for now, and maybe we can explore adding something to core in the future.

Something like this plugin would generate group-2-hover and group-3-hover variants that you could use to sort of "name" the nested groups and avoid collisions:

const selectorParser = require('postcss-selector-parser')

module.exports = function ({ addVariant }) {
  addVariant('group-2-hover', ({ modifySelectors, separator }) => {
    return modifySelectors(({ selector }) => {
      return selectorParser(selectors => {
        selectors.walkClasses(sel => {
          sel.value = `group-2-hover${separator}${sel.value}`
          sel.parent.insertBefore(sel, selectorParser().astSync(prefixSelector(config.prefix, '.group-2:hover '))
          )
        })
      }).processSync(selector)
    })
  })

  addVariant('group-3-hover', ({ modifySelectors, separator }) => {
    return modifySelectors(({ selector }) => {
      return selectorParser(selectors => {
        selectors.walkClasses(sel => {
          sel.value = `group-3-hover${separator}${sel.value}`
          sel.parent.insertBefore(sel, selectorParser().astSync(prefixSelector(config.prefix, '.group-3:hover '))
          )
        })
      }).processSync(selector)
    })
  })
}

@christopher4lis
Copy link

Any chance there's an up-to-date version of the plugin above? Just tried it out with tailwindcss v1.4.6 and don't seem to be having any luck.

@ErickTamayo
Copy link

@christopher4lis I created a small plugin to be able to have what I call "named groups" that is similar to what @adamwathan does here.

You can take a look it here: https://github.com/ErickTamayo/tailwindcss-named-groups

@AndyOGo
Copy link

AndyOGo commented Feb 12, 2021

I released support for nested groups by scoping https://github.com/AndyOGo/tailwindcss-nested-groups

You can play with it here
https://play.tailwindcss.com/LynJHjqDNU

@pdevito3
Copy link

@adamwathan @simonswiss any more recent thoughts on this? would be cool if group was scoped by default or if we had a way to name them. not sure if that's built into JIT?

@LeaHabel
Copy link

@AndyOGo thanks for your plugin! Really love it :) Unfortunatly it doesn't work with the latest tailwind/postcss version. Any plans to update it?

@AndyOGo
Copy link

AndyOGo commented Sep 21, 2021

@LeaHabel
Thank you.
I would need more data and ideally a reprodution repo.

Anyway, just in case you have JIT enabled?
The thing is JIT broke many things and tailwind still has some JIT related issues.
https://github.com/tailwindlabs/tailwindcss/issues?q=is%3Aissue+is%3Aopen+jit

@klausXR
Copy link

klausXR commented Nov 12, 2021

@adamwathan can you perhaps look into this after the JIT implementation? It would be very convenient to generate these at runtime. That plugin works, but they must be manually configured.

@yoobi
Copy link

yoobi commented Dec 8, 2021

@adamwathan

I would recommend solving this problem with a custom variant plugin for now, and maybe we can explore adding something to core in the future.

Something like this plugin would generate group-2-hover and group-3-hover variants that you could use to sort of "name" the nested groups and avoid collisions:

const selectorParser = require('postcss-selector-parser')

module.exports = function ({ addVariant }) {
  addVariant('group-2-hover', ({ modifySelectors, separator }) => {
    return modifySelectors(({ selector }) => {
      return selectorParser(selectors => {
        selectors.walkClasses(sel => {
          sel.value = `group-2-hover${separator}${sel.value}`
          sel.parent.insertBefore(sel, selectorParser().astSync(prefixSelector(config.prefix, '.group-2:hover '))
          )
        })
      }).processSync(selector)
    })
  })

  addVariant('group-3-hover', ({ modifySelectors, separator }) => {
    return modifySelectors(({ selector }) => {
      return selectorParser(selectors => {
        selectors.walkClasses(sel => {
          sel.value = `group-3-hover${separator}${sel.value}`
          sel.parent.insertBefore(sel, selectorParser().astSync(prefixSelector(config.prefix, '.group-3:hover '))
          )
        })
      }).processSync(selector)
    })
  })
}

Hello, I'm rather new to that and I've tried your code with v2.2.19 but it doesn't work. I've look at the documentation but there is nothing similar to it :/
prefixSelector and config are an unknown for me

// tailwind.config.js
const { selectorParser } = require('postcss-selector-parser');
const plugin = require('tailwindcss');
const { colors } = require('./styles/theme/beta/color');

// https://tailwindcss.com/docs/adding-new-utilities
module.exports = {
  purge: ['./pages/**/*.{js,ts,jsx,tsx}', './src/components/**/*.{js,ts,jsx,tsx}'],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {
      fontSize: {
        xs: '.70rem',
      },
      colors: {
        ...colors,
      },
      ...
    },
  },
  variants: {
    extend: {},
  },
  plugins: [
      plugin(
          ({ addVariant }) => {
            addVariant('group-2-hover', ({
                                           modifySelectors,
                                           separator,
                                         }) => modifySelectors(({ selector }) => selectorParser((selectors) => {
                  selectors.walkClasses((sel) => {
                    sel.value = `group-2-hover${separator}${sel.value}`;
                    sel.parent.insertBefore(sel, selectorParser()
                        .astSync(prefixSelector(config.prefix, '.group-2:hover ')));
                  });
                })
                    .processSync(selector)));
          },
      ),
  ],
};

@tonioriol
Copy link

@yoobi couldn't make @adamwathan example work either.

@AndyOGo plugin doesnt work for me either.

The one that worked for me is https://github.com/ErickTamayo/tailwindcss-named-groups from @ErickTamayo, thanks man :)

@maelquerre
Copy link

maelquerre commented Mar 16, 2022

By looking in the source code of Tailwind CSS, I found a very concise way to add the named groups hover feature in the config, rather than installing a package. It works well (v3.0.13), not sure it's something safe to use in production though.

const plugin = require('tailwindcss/plugin')

module.exports = {
  theme: {
    groups: ['example']
  },
  plugins: [
    plugin(({ addVariant, theme }) => {
      const groups = theme('groups') || []

      groups.forEach((group) => {
        addVariant(`group-${group}-hover`, () => {
          return `:merge(.group-${group}):hover &`
        })
      })
    })
  ]
}

@leo
Copy link

leo commented Mar 27, 2022

Thanks for the suggestion above, @maelquerre! 👏 It works quite well.

I think that, at the moment, that's really the cleanest way to do it.

In fact, I liked it so much that I extended it a bit and bundled it into a tailwind-custom-groups package, in case anyone wants to install it quickly (like me).

@AndyOGo
Copy link

AndyOGo commented Mar 27, 2022

@maelquerre
Thanks for sharing.
What is the :merge doing exactly?

@anankitpatil
Copy link

anankitpatil commented Apr 1, 2022

plugin(({ addVariant, theme }) => {
      const groups = theme('groups') || []

      groups.forEach((group) => {
        addVariant(`group-${group}-hover`, () => {
          return `:merge(.group-${group}):hover &`
        })
      })
    })

This is not working for me... 2.1.3

theme: {
groups: ['ved'],.....

plugins: [
require('@tailwindcss/forms'),
require('tailwindcss-truncate-multiline')(),
plugin(({ addVariant, theme }) => {
const groups = theme('groups') || [];
groups.forEach((group) => {
addVariant(group-${group}-hover, () => {
return :merge(.group-${group}):hover &;
});
});
}),
],

Am i doing something wrong?

@ghost
Copy link

ghost commented Apr 20, 2022

@anankitpatil
I am using version 3.0.23 and it works.
If you have something like groups: ["example"] you can use it like this:

<div class="group-example">
    <button>Dropdown</button>
    <div class="absolute hidden group-example-hover:block">
        <p>Content</p>
    </div>
</div>

@onmax
Copy link

onmax commented Apr 20, 2022

You can also use tailwindcss-labeled-groups 😄

@moinulmoin
Copy link

moinulmoin commented Apr 23, 2022

Thanks for the suggestion above, @maelquerre! 👏 It works quite well.

I think that, at the moment, that's really the cleanest way to do it.

In fact, I liked it so much that I extended it a bit and bundled it into a tailwind-custom-groups package, in case anyone wants to install it quickly (like me).

I am using it. Works well!. Thanks

@boompikachu
Copy link

With TailwindCSS v3.1, we can use arbitrary variant to create nested group

<div class="group">
  <div>Parent</div>
  <div class="invisible group-hover:visible">
    <div class="group-2">
      <div>Child</div>
      <div class="invisible [.group-2:hover_&]:visible">Grandchild</div>
    </div>
  </div>
</div>

Here's the playground https://play.tailwindcss.com/wYJHX4z5fi

@leo
Copy link

leo commented Sep 4, 2022

Thanks for sharing, @boompikachu! That's an interesting approach.

It seems to rely on the newly added Arbitrary Variants feature, though, which means the code now contains an arbitrarily defined class name.

That's a first-class citizen feature of Tailwind CSS, so I think it's totally fine to use.

In our case, however, we're using the tailwindcss/no-arbitrary-value ESLint rule provided by eslint-plugin-tailwindcss to prevent any arbitrary class names in our code, because we find it to be cleaner. Like this, all the possible class names are visible in and controlled from a single place: The Tailwind CSS config. Like that, it's impossible that someone could accidentally make the layout quite complex by adding lots of arbitrary classes that deviate from the main styling system.

Because of that, we'll continue maintaining and using tailwindcss-custom-groups.

Thanks for the tip, though!

@ahjam-taoufik
Copy link

You can also use tailwindcss-labeled-groups 😄

very nice solution ; it work

@otanim
Copy link

otanim commented Dec 2, 2022

The issue seems to have existed in v3.1.8 but has been fixed in v3.2.4.

@nelisbijl
Copy link

With TailwindCSS v3.1, we can use arbitrary variant to create nested group

<div class="group">
  <div>Parent</div>
  <div class="invisible group-hover:visible">
    <div class="group-2">
      <div>Child</div>
      <div class="invisible [.group-2:hover_&]:visible">Grandchild</div>
    </div>
  </div>
</div>

Here's the playground https://play.tailwindcss.com/wYJHX4z5fi

I had success with using [.group:hover_>_&]:bg-g-400 in Tailwind 3; only for direct childs!
It is useful for dynamic nesting where you do not know the nesting level
And even if you do, processing :class="{[[group-${level}:hover_&]:visible]: condition}" (vue) didn't work well

@royvanv
Copy link

royvanv commented Jan 13, 2023

And even if you do, processing :class="{[[group-${level}:hover_&]:visible]: condition}" (vue) didn't work well

That's because Tailwind can't match the class when purging. See Dynamic class names for more information about class matching.

You have a few options:

  1. Map level to a class within the controller (I'm not familiar enough with Vue to provide a complete example, but something like { 'name': '[group-name:hover_&]:visible' } should work).
  2. Safelisting the classes in your config.
  3. And the hardest option: building a custom extractor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests