---
title: "is-* variants"
description: "is-[…], group-is-* and peer-is-* for :is() selectors."
requested_language: cs
language: en
translation_notice: "This page isn't translated yet"
url: https://docs.systhema.app/cs/styling/is-variants
version: unreleased (main)
docs_index: https://docs.systhema.app/cs/llms.txt
---
> This page isn't translated yet. Showing English.


The `is-[…]` variant applies a utility when the element matches the selector list in the brackets, through CSS `:is()`. `group-is-[…]` does the same for the nearest `group` ancestor and `peer-is-[…]` for a preceding `peer` sibling, so state classes that a script toggles (`.active`, `.is-open`) can drive styles without custom CSS.

## Quick reference

<!-- generated:utilities is-variants -->

| Variant               | Applies                                | Note                                               |
| --------------------- | -------------------------------------- | -------------------------------------------------- |
| `is-[.active]:`       | `&:is(.active)`                        | The element matches the selector in brackets.      |
| `group-is-[.active]:` | `&:is(:where(.group):is(.active) *)`   | The closest `group` ancestor matches the selector. |
| `peer-is-[:checked]:` | `&:is(:where(.peer):is(:checked) ~ *)` | A preceding `peer` sibling matches the selector.   |

<!-- /generated -->

## Basic usage

`is-[.active]:font-bold` styles the element while it has the `active` class. Any selector `:is()` accepts goes in the brackets, including a comma-separated list: `is-[:hover,:focus-visible]:underline` matches either state. Click the tabs below; the script only moves the `active` class, and the variant does the rest:

```tsx preview title="is-[.active]"
import { useState } from 'react'

const tabs = ['Overview', 'Pricing', 'Reviews']

export default function Demo() {
  const [current, setCurrent] = useState('Overview')

  return (
    <div className="flex gap-1 rounded-lg border border-(--color-foundations-surface-border) p-1">
      {tabs.map((tab) => (
        <button
          key={tab}
          type="button"
          aria-pressed={tab === current}
          onClick={() => setCurrent(tab)}
          className={`rounded-md px-4 py-2 color-body is-[:hover,:focus-visible]:underline is-[.active]:bg-(--color-foundations-primary-bg) is-[.active]:text-(--color-foundations-primary-text) ${tab === current ? 'active' : ''}`}
        >
          {tab}
        </button>
      ))}
    </div>
  )
}
```

```html
<button class="rounded-md px-4 py-2 is-[.active]:bg-(--color-foundations-primary-bg) active">
  Overview
</button>
```

## Group and peer

`group-is-[…]` styles a child by the state of an ancestor marked `group`. That fits components whose state lives on the root, such as an open menu or an expanded panel:

```tsx preview title="group-is-[.open]"
import { useState } from 'react'

export default function Demo() {
  const [open, setOpen] = useState(false)

  return (
    <div
      className={`group w-full max-w-[24rem] rounded-lg border border-(--color-foundations-surface-border) ${open ? 'open' : ''}`}
    >
      <button
        type="button"
        aria-expanded={open}
        onClick={() => setOpen(!open)}
        className="flex w-full items-center justify-between px-4 py-3 color-heading"
      >
        Shipping details
        <svg
          aria-hidden="true"
          focusable="false"
          viewBox="0 0 16 16"
          className="h-4 w-4 transition-transform group-is-[.open]:rotate-180"
        >
          <path d="M3 6l5 5 5-5" fill="none" stroke="currentColor" strokeWidth="1.5" />
        </svg>
      </button>
      <p className="hidden px-4 pb-4 text-small color-body group-is-[.open]:block">
        Orders ship within two working days.
      </p>
    </div>
  )
}
```

`peer-is-[…]` styles an element by the state of an earlier sibling marked `peer`. Type a malformed address and leave the field: the hint appears without a line of script.

```tsx preview title="peer-is-[…]"
export default function Demo() {
  return (
    <div className="flex w-full max-w-[24rem] flex-col gap-2">
      <label htmlFor="is-demo-email" className="form-label">
        Email
      </label>
      <input
        id="is-demo-email"
        type="email"
        placeholder="you@example.com"
        className="peer form-input"
      />
      <p className="hidden text-small text-(--color-foundations-destructive) peer-is-[:invalid:not(:placeholder-shown)]:block">
        Enter a valid email address.
      </p>
    </div>
  )
}
```

Both take Tailwind's named form when groups or peers nest: mark the ancestor `group/menu` and write `group-is-[.open]/menu:block`; mark the sibling `peer/email` and write `peer-is-[:invalid]/email:block`.

## Responsive and state variants

The `is-*` variants stack with breakpoint prefixes and with each other: `md:is-[.active]:bg-base-100` applies from `md` up, `mm-sm:group-is-[.open]:block` only on phones (see [Breakpoints and ranges](https://docs.systhema.app/cs/styling/breakpoints.md)).

They pair well with the state classes Systhema's own listeners write: the header menu toggles `is-open`, scroll listeners write `scroll-on-top` and `is-scrolling-down` on `<body>`, and revealed elements get `animated`. For example, `group-is-[.scroll-on-top]:shadow-none` on a header child with `group` on `<body>` (see [Listeners](https://docs.systhema.app/cs/components/listeners.md)).

## Customizing

There is nothing to configure: the selector is whatever you put in the brackets. Spaces are not allowed inside a class name, so write them as underscores, as in any Tailwind arbitrary value: `is-[.card_>_.title]:` matches `.card > .title`.

## Related

- [Breakpoints and ranges](https://docs.systhema.app/cs/styling/breakpoints.md)
- [Listeners](https://docs.systhema.app/cs/components/listeners.md)
- [Variants reference](https://docs.systhema.app/cs/reference/utilities/variants.md)
