Group Selector

Group selectors allow child elements to respond to interactions on a parent element. This is useful for creating interactive cards, list items, and other composite components.

Basic Usage

Add the data-group attribute to a parent element, then use _groupHover, _groupFocus, or _groupActive on children:

1<Box bg="$background" borderRadius="8px" data-group p={4}>
2  <Text _groupHover={{ color: '$primary' }}>
3    This text changes color when parent is hovered
4  </Text>
5  <Box _groupHover={{ bg: '$primaryAlpha10' }} p={2}>
6    This box changes background on parent hover
7  </Box>
8</Box>
1<Box bg="$background" borderRadius="8px" data-group p={4}>
2  <Text _groupHover={{ color: '$primary' }}>
3    This text changes color when parent is hovered
4  </Text>
5  <Box _groupHover={{ bg: '$primaryAlpha10' }} p={2}>
6    This box changes background on parent hover
7  </Box>
8</Box>

Migrating from role="group": previous versions used role="group" to mark group parents. Both forms are supported today, but role="group" carries ARIA semantics that should be reserved for actual semantic groups (e.g. a <fieldset> replacement), not for styling hooks. Prefer data-group going forward. The role="group" form will be removed in v2 — see the CHANGELOG.

Available Group Selectors

SelectorTriggered when
_groupHover

Parent with data-group is hovered

_groupFocus

Parent with data-group is focused

_groupActive

Parent with data-group is active (pressed)

_groupFocusWithinAny element inside the group receives focus

Interactive Card Example

1<Box
2  _hover={{ boxShadow: '0 4px 12px rgba(0,0,0,0.15)' }}
3  bg="white"
4  borderRadius="8px"
5  boxShadow="0 1px 3px rgba(0,0,0,0.1)"
6  cursor="pointer"
7  data-group
8  p={4}
9>
10  <Box
11    bg="$backgroundMuted"
12    borderRadius="4px"
13    h="200px"
14    overflow="hidden"
15    w="100%"
16  >
17    <Image
18      _groupHover={{ transform: 'scale(1.05)' }}
19      src="/image.jpg"
20      transition="transform 0.2s"
21    />
22  </Box>
23
24  <Text _groupHover={{ color: '$primary' }} fontWeight={600} mt={3}>
25    Card Title
26  </Text>
27
28  <Text color="$textMuted" fontSize="14px">
29    Card description goes here
30  </Text>
31
32  <Box
33    _groupHover={{ opacity: 1 }}
34    mt={3}
35    opacity={0}
36    transition="opacity 0.2s"
37  >
38    <Button>View Details</Button>
39  </Box>
40</Box>
1<Box
2  _hover={{ boxShadow: '0 4px 12px rgba(0,0,0,0.15)' }}
3  bg="white"
4  borderRadius="8px"
5  boxShadow="0 1px 3px rgba(0,0,0,0.1)"
6  cursor="pointer"
7  data-group
8  p={4}
9>
10  <Box
11    bg="$backgroundMuted"
12    borderRadius="4px"
13    h="200px"
14    overflow="hidden"
15    w="100%"
16  >
17    <Image
18      _groupHover={{ transform: 'scale(1.05)' }}
19      src="/image.jpg"
20      transition="transform 0.2s"
21    />
22  </Box>
23
24  <Text _groupHover={{ color: '$primary' }} fontWeight={600} mt={3}>
25    Card Title
26  </Text>
27
28  <Text color="$textMuted" fontSize="14px">
29    Card description goes here
30  </Text>
31
32  <Box
33    _groupHover={{ opacity: 1 }}
34    mt={3}
35    opacity={0}
36    transition="opacity 0.2s"
37  >
38    <Button>View Details</Button>
39  </Box>
40</Box>

List Item Example

1<Box as="ul" listStyle="none" m={0} p={0}>
2  {items.map((item) => (
3    <Box
4      key={item.id}
5      _hover={{ bg: '$backgroundMuted' }}
6      as="li"
7      borderBottom="1px solid $border"
8      data-group
9      p={3}
10    >
11      <Flex align="center" justify="space-between">
12        <Text _groupHover={{ color: '$primary' }}>{item.title}</Text>
13        <Box _groupHover={{ opacity: 1 }} opacity={0}>
14          <Button size="sm">Edit</Button>
15        </Box>
16      </Flex>
17    </Box>
18  ))}
19</Box>
1<Box as="ul" listStyle="none" m={0} p={0}>
2  {items.map((item) => (
3    <Box
4      key={item.id}
5      _hover={{ bg: '$backgroundMuted' }}
6      as="li"
7      borderBottom="1px solid $border"
8      data-group
9      p={3}
10    >
11      <Flex align="center" justify="space-between">
12        <Text _groupHover={{ color: '$primary' }}>{item.title}</Text>
13        <Box _groupHover={{ opacity: 1 }} opacity={0}>
14          <Button size="sm">Edit</Button>
15        </Box>
16      </Flex>
17    </Box>
18  ))}
19</Box>

Navigation Menu Example

1<Box as="nav">
2  <Box _hover={{ bg: '$backgroundMuted' }} borderRadius="4px" data-group p={2}>
3    <Flex align="center" gap={2}>
4      <Box
5        _groupHover={{ bg: '$primary' }}
6        bg="$textMuted"
7        borderRadius="50%"
8        h="8px"
9        w="8px"
10      />
11      <Text _groupHover={{ color: '$primary' }}>Menu Item</Text>
12      <Box _groupHover={{ opacity: 1 }} ml="auto" opacity={0}>
1314      </Box>
15    </Flex>
16  </Box>
17</Box>
1<Box as="nav">
2  <Box _hover={{ bg: '$backgroundMuted' }} borderRadius="4px" data-group p={2}>
3    <Flex align="center" gap={2}>
4      <Box
5        _groupHover={{ bg: '$primary' }}
6        bg="$textMuted"
7        borderRadius="50%"
8        h="8px"
9        w="8px"
10      />
11      <Text _groupHover={{ color: '$primary' }}>Menu Item</Text>
12      <Box _groupHover={{ opacity: 1 }} ml="auto" opacity={0}>
1314      </Box>
15    </Flex>
16  </Box>
17</Box>

Combining with Responsive Styles

Group selectors can be combined with responsive arrays:

1<Box data-group p={[2, null, 4]}>
2  <Text
3    _groupHover={{
4      color: ['$primary', null, '$secondary'],
5      fontSize: ['16px', null, '18px'],
6    }}
7  >
8    Responsive group hover
9  </Text>
10</Box>
1<Box data-group p={[2, null, 4]}>
2  <Text
3    _groupHover={{
4      color: ['$primary', null, '$secondary'],
5      fontSize: ['16px', null, '18px'],
6    }}
7  >
8    Responsive group hover
9  </Text>
10</Box>

Nested Groups

For nested groups, the selector applies to the nearest parent with data-group:

1<Box bg="white" data-group p={4}>
2  <Text _groupHover={{ color: 'blue' }}>Responds to outer group</Text>
3
4  <Box bg="$backgroundMuted" data-group mt={2} p={2}>
5    <Text _groupHover={{ color: 'red' }}>Responds to inner group</Text>
6  </Box>
7</Box>
1<Box bg="white" data-group p={4}>
2  <Text _groupHover={{ color: 'blue' }}>Responds to outer group</Text>
3
4  <Box bg="$backgroundMuted" data-group mt={2} p={2}>
5    <Text _groupHover={{ color: 'red' }}>Responds to inner group</Text>
6  </Box>
7</Box>

CSS Output

1<Box data-group>
2  <Box _groupHover={{ bg: 'red' }} />
3</Box>
1<Box data-group>
2  <Box _groupHover={{ bg: 'red' }} />
3</Box>

Generates CSS like:

1:is([role='group'], [data-group]):hover .a {
2  background-color: red;
3}
1:is([role='group'], [data-group]):hover .a {
2  background-color: red;
3}

The :is() wrapper matches both the new data-group attribute and the legacy role="group" attribute with a single rule, so existing markup keeps working during the migration window.

Best Practices

  1. Use data-group for styling hooks: Reserve role="group" for elements that genuinely form an ARIA group; use the data attribute for visual grouping that has no a11y semantics.
  2. Keep interactions discoverable: Don't hide critical content behind group hover.
  3. Consider touch devices: Group hover doesn't work on touch — provide alternative interactions.
  4. Use transitions: Add transition prop for smooth state changes.
  5. Accessibility: Ensure interactive elements are keyboard accessible.