---
title: Conditional Styles
description: Learn how to use conditional and responsive styles in Bamboo.
---

When writing styles, you might need to apply specific changes depending on a specific condition, whether it's based on
breakpoint, css pseudo state, media query or custom data attributes.

Bamboo allows you to write conditional styles, and provides common condition shortcuts to make your life easier. Let's
say you want to change the background color of a button when it's hovered. You can do it like this:

```jsx
<button
  className={css({
    bg: 'red.500',
    _hover: { bg: 'red.700' },
  })}
>
  Hover me
</button>
```

## Overview

### Property based condition

This works great, but might be a bit verbose. You can apply the condition `_hover` directly to the `bg` property,
leading to a more concise syntax:

```diff
<button
  className={css({
-   bg: 'red.500',
-   _hover: { bg: 'red.700' }
+   bg: { base: 'red.500', _hover: 'red.700' }
  })}
>
  Hover me
</button>
```

> Note: The `base` key is used to define the default value of the property, without any condition.

### Nested condition

Conditions in Bamboo can be nested, which means you can apply multiple conditions to a single property or another
condition.

Let's say you want to change the background color of a button when it's focused and hovered. You can do it like this:

```jsx
<button
  className={css({
    bg: { base: 'red.500', _hover: { _focus: 'red.700' } },
  })}
>
  Hover me
</button>
```

### Built-in conditions

Bamboo includes a set of common pseudo states that you can use to style your components:

- Pseudo Class: `_hover`, `_active`, `_focus`, `_focusVisible`, `_focusWithin`, `_disabled`
- Pseudo Element: `_before`, `_after`
- Media Query: `sm`, `md`, `lg`, `xl`, `2xl`
- Data Attribute Selector: `_horizontal`, `_vertical`, `_portrait`, `_landscape`

## Arbitrary selectors

What if you need a one-off selector that is not defined in your config's conditions? You can use the `css` function to
generate classes for arbitrary selectors:

```tsx
import { css } from '../styled-system/css'

const App = () => {
  return (
    <div
      className={css({
        '&[data-state=closed]': { color: 'red.300' },
        '& > *': { margin: '2' },
      })}
    />
  )
}
```

This also works with the supported at-rules (`@media`, `@layer`, `@container`, `@supports`, and `@page`):

```tsx
import { css } from '../styled-system/css'

const App = () => {
  return (
    <div className={css({ display: 'flex', containerType: 'size' })}>
      <div
        className={css({
          '@media (min-width: 768px)': {
            color: 'red.300',
          },
          '@container (min-width: 10px)': {
            color: 'green.300',
          },
          '@supports (display: flex)': {
            fontSize: '3xl',
            color: 'blue.300',
          },
        })}
      />
    </div>
  )
}
```

## Pseudo Classes

Every pseudo class in the [Reference](#reference) has a `_` modifier that works like the `_hover` above — `_active`,
`_focus`, `_disabled`, `_first`, `_last`, `_even`, `_odd`, and so on:

```jsx
<table>
  <tbody>
    {items.map((item) => (
      <tr
        key={item}
        className={css({
          _even: { bg: 'gray.100' },
          _odd: { bg: 'white' },
        })}
      >
        <td>{item}</td>
      </tr>
    ))}
  </tbody>
</table>
```

## Pseudo Elements

### Before and After

You can style the `::before` and `::after` pseudo elements of an element using their `_before` and `_after` modifier:

```jsx
<div
  className={css({
    _before: { content: '"👋"' },
  })}
>
  Hello
</div>
```

#### Notes

- **Before and After**: Ensure you wrap the content value in double quotes.
- **Mixing with Conditions**: When using condition and pseudo elements, prefer to place the condition **before** the
  pseudo element.

```jsx
css({
  // This works ✅
  _dark: { _backdrop: { color: 'red' } },
  // This doesn't work ❌
  _backdrop: { _dark: { color: 'red' } },
})
```

The reason `_backdrop: { _dark: { color: 'red' } }` doesn't work is because it generated an invalid CSS structure that
looks like:

```css
&::backdrop {
  &.dark,
  .dark & {
    color: red;
  }
}
```

### Placeholder

Style the placeholder text of any input or textarea using the `_placeholder` modifier:

```jsx
<input
  placeholder="Enter your name"
  className={css({
    _placeholder: { color: 'gray.500' },
  })}
/>
```

### File Inputs

Style the file input button using the `_file` modifier:

```jsx
<input
  type="file"
  className={css({
    _file: { bg: 'gray.500', px: '4', py: '2', marginEnd: '3' },
  })}
/>
```

## Media Queries

User-preference media features have `_` modifiers of their own, and compose like any other condition:

```jsx
<div
  className={css({
    bg: 'white',
    _osDark: { bg: 'black' },
    _motionReduce: { transition: 'none' },
  })}
>
  Hello
</div>
```

| Modifiers                                           | Media query                                                |
| --------------------------------------------------- | ---------------------------------------------------------- |
| `_motionReduce` / `_motionSafe`                     | `prefers-reduced-motion: reduce` / `no-preference`         |
| `_osDark` / `_osLight`                              | `prefers-color-scheme: dark` / `light`                     |
| `_lessContrast` / `_moreContrast` / `_highContrast` | `prefers-contrast: less` / `more`, `forced-colors: active` |
| `_portrait` / `_landscape`                          | `orientation: portrait` / `landscape`                      |

## Group Selectors

When you need to style an element based on its parent element's state or attribute, you can add the `group` class to the
parent element, and use any of the `_group*` modifiers on the child element.

```jsx
<div className="group">
  <p className={css({ _groupHover: { bg: 'red.500' } })}>Hover me</p>
</div>
```

This modifer for every pseudo class modifiers like `_groupHover`, `_groupActive`, `_groupFocus`, and `_groupDisabled`,
etc.

## Sibling Selectors

When you need to style an element based on its sibling element's state or attribute, you can add the `peer` class to the
sibling element, and use any of the `_peer*` modifiers on the target element.

```jsx
<div>
  <p className="peer">Hover me</p>
  <p className={css({ _peerHover: { bg: 'red.500' } })}>I'll change by bg</p>
</div>
```

> Note: This only works for when the element marked with `peer` is a previous siblings, that is, it comes before the
> element you want to start.

## Data Attribute

### LTR and RTL

You can style an element based on the direction of the text using the `_ltr` and `_rtl` modifiers:

```jsx
<div dir="ltr">
  <div
    className={css({
      _ltr: { ml: '3' },
      _rtl: { mr: '3' },
    })}
  >
    Hello
  </div>
</div>
```

For this to work, you need to set the `dir` attribute on the parent element. In most cases,you can set this on the
`html` element.

> **Note:** Consider using logical css properties like `marginInlineStart` and `marginInlineEnd` instead their physical
> counterparts like `marginLeft` and `marginRight`. This will reduce the need to use the `_ltr` and `_rtl` modifiers.

### State

You can style an element based on its `data-{state}` attribute using the corresponding `_{state}` modifier:

```jsx
<div
  data-loading
  className={css({
    _loading: { bg: 'gray.500' },
  })}
>
  Hello
</div>
```

This also works for common states like `data-active`, `data-disabled`, `data-focus`, `data-hover`, `data-invalid`,
`data-required`, and `data-valid`.

```jsx
<div
  data-active
  className={css({
    _active: { bg: 'gray.500' },
  })}
>
  Hello
</div>
```

> Most of the `data-{state}` attributes typically mirror the corresponding browser pseudo class. For example,
> `data-hover` is equivalent to `:hover`, `data-focus` is equivalent to `:focus`, and `data-active` is equivalent to
> `:active`.

### Orientation

You can style an element based on its `data-orientation` attribute using the `_horizontal` and `_vertical` modifiers:

```jsx
<div
  data-orientation="horizontal"
  className={css({
    _horizontal: { bg: 'red.500' },
    _vertical: { bg: 'blue.500' },
  })}
>
  Hello
</div>
```

## ARIA Attribute

You can style an element based on its `aria-{state}=true` attribute using the corresponding `_{state}` modifier:

```jsx
<div
  aria-expanded="true"
  className={css({
    _expanded: { bg: 'gray.500' },
  })}
>
  Hello
</div>
```

> Most of the `aria-{state}` attributes typically mirror the support ARIA states in the browser pseudo class. For
> example, `aria-checked=true` is styled with `_checked`, `aria-disabled=true` is styled with `_disabled`.

## Container queries

You can define container names and sizes in your theme configuration and use them in your styles.

```ts
export default defineConfig({
  // ...
  theme: {
    extend: {
      containerNames: ['sidebar', 'content'],
      containerSizes: {
        xs: '40em',
        sm: '60em',
        md: '80em',
      },
    },
  },
})
```

The default container sizes in the `@bamboocss/preset-bamboo` preset are shown below:

```ts
export const containerSizes = {
  xs: '320px',
  sm: '384px',
  md: '448px',
  lg: '512px',
  xl: '576px',
  '2xl': '672px',
  '3xl': '768px',
  '4xl': '896px',
  '5xl': '1024px',
  '6xl': '1152px',
  '7xl': '1280px',
  '8xl': '1440px',
}
```

Then use them in your styles by referencing using `@<container-name>/<container-size>` syntax:

> The default container syntax is `@/<container-size>`.

```ts
import { css } from '/styled-system/css'

function Demo() {
  return (
    <nav className={css({ containerType: 'inline-size' })}>
      <div
        className={css({
          fontSize: { '@/sm': 'md' }
        })}
      />
    </nav>
  )
}
```

This will generate the following CSS:

```css
.cq-t_inline-size {
  container-type: inline-size;
}

@container (inline-size >= 60rem) {
  .\@\/sm\:fs_md {
    font-size: var(--font-sizes-md);
  }
}
```

Container sizes carry the same range set as breakpoints. Alongside `@/sm`, which is open-ended upwards, you get
`@/smOnly` for that size alone, `@/smDown` for everything below it, and `@/smToLg` for a span:

```css
@container (inline-size >= 30em) { … }                            /* @/sm     */
@container (inline-size >= 30em) and (inline-size < 40em) { … }   /* @/smOnly */
@container (inline-size < 30em) { … }                             /* @/smDown */
```

As with breakpoints, the upper bound is exclusive, so `@/smOnly` stops exactly where `@/md` starts.

You can also named container queries:

```ts
import { css } from 'styled-system/css'

function Demo() {
  return (
    <nav className={css({ containerType: 'inline-size', containerName: 'sidebar' })}>
      <div
        className={css({
          fontSize: { base: 'lg', '@sidebar/sm': 'md' }
        })}
      />
    </nav>
  )
}
```

## Reference

Here's a list of all the condition shortcuts you can use in Bamboo:

| Condition name         | Selector                                                                                         |
| ---------------------- | ------------------------------------------------------------------------------------------------ |
| \_hover                | `&:is(:hover, [data-hover])`                                                                     |
| \_focus                | `&:is(:focus, [data-focus])`                                                                     |
| \_focusWithin          | `&:focus-within`                                                                                 |
| \_focusVisible         | `&:is(:focus-visible, [data-focus-visible])`                                                     |
| \_disabled             | `&:is(:disabled, [disabled], [data-disabled], [aria-disabled=true])`                             |
| \_active               | `&:is(:active, [data-active])`                                                                   |
| \_visited              | `&:visited`                                                                                      |
| \_target               | `&:target`                                                                                       |
| \_readOnly             | `&:is(:read-only, [data-read-only], [aria-readonly=true])`                                       |
| \_readWrite            | `&:read-write`                                                                                   |
| \_empty                | `&:is(:empty, [data-empty])`                                                                     |
| \_checked              | `&:is(:checked, [data-checked], [aria-checked=true], [data-state="checked"])`                    |
| \_enabled              | `&:enabled`                                                                                      |
| \_expanded             | `&:is([aria-expanded=true], [data-expanded], [data-state="expanded"])`                           |
| \_highlighted          | `&[data-highlighted]`                                                                            |
| \_complete             | `&[data-complete]`                                                                               |
| \_incomplete           | `&[data-incomplete]`                                                                             |
| \_dragging             | `&[data-dragging]`                                                                               |
| \_before               | `&::before`                                                                                      |
| \_after                | `&::after`                                                                                       |
| \_firstLetter          | `&::first-letter`                                                                                |
| \_firstLine            | `&::first-line`                                                                                  |
| \_marker               | `&::marker, &::-webkit-details-marker`                                                           |
| \_selection            | `&::selection`                                                                                   |
| \_file                 | `&::file-selector-button`                                                                        |
| \_backdrop             | `&::backdrop`                                                                                    |
| \_first                | `&:first-child`                                                                                  |
| \_last                 | `&:last-child`                                                                                   |
| \_only                 | `&:only-child`                                                                                   |
| \_even                 | `&:nth-child(even)`                                                                              |
| \_odd                  | `&:nth-child(odd)`                                                                               |
| \_firstOfType          | `&:first-of-type`                                                                                |
| \_lastOfType           | `&:last-of-type`                                                                                 |
| \_onlyOfType           | `&:only-of-type`                                                                                 |
| \_peerFocus            | `.peer:is(:focus, [data-focus]) ~ &`                                                             |
| \_peerHover            | `.peer:is(:hover, [data-hover]) ~ &`                                                             |
| \_peerActive           | `.peer:is(:active, [data-active]) ~ &`                                                           |
| \_peerFocusWithin      | `.peer:focus-within ~ &`                                                                         |
| \_peerFocusVisible     | `.peer:is(:focus-visible, [data-focus-visible]) ~ &`                                             |
| \_peerDisabled         | `.peer:is(:disabled, [disabled], [data-disabled], [aria-disabled=true]) ~ &`                     |
| \_peerChecked          | `.peer:is(:checked, [data-checked], [aria-checked=true], [data-state="checked"]) ~ &`            |
| \_peerInvalid          | `.peer:is(:invalid, [data-invalid], [aria-invalid=true]) ~ &`                                    |
| \_peerExpanded         | `.peer:is([aria-expanded=true], [data-expanded], [data-state="expanded"]) ~ &`                   |
| \_peerPlaceholderShown | `.peer:placeholder-shown ~ &`                                                                    |
| \_groupFocus           | `.group:is(:focus, [data-focus]) &`                                                              |
| \_groupHover           | `.group:is(:hover, [data-hover]) &`                                                              |
| \_groupActive          | `.group:is(:active, [data-active]) &`                                                            |
| \_groupFocusWithin     | `.group:focus-within &`                                                                          |
| \_groupFocusVisible    | `.group:is(:focus-visible, [data-focus-visible]) &`                                              |
| \_groupDisabled        | `.group:is(:disabled, [disabled], [data-disabled], [aria-disabled=true]) &`                      |
| \_groupChecked         | `.group:is(:checked, [data-checked], [aria-checked=true], [data-state="checked"]) &`             |
| \_groupExpanded        | `.group:is([aria-expanded=true], [data-expanded], [data-state="expanded"]) &`                    |
| \_groupInvalid         | `.group:is(:invalid, [data-invalid], [aria-invalid=true]) &`                                     |
| \_indeterminate        | `&:is(:indeterminate, [data-indeterminate], [aria-checked=mixed], [data-state="indeterminate"])` |
| \_required             | `&:is(:required, [data-required], [aria-required=true])`                                         |
| \_valid                | `&:is(:valid, [data-valid])`                                                                     |
| \_invalid              | `&:is(:invalid, [data-invalid], [aria-invalid=true])`                                            |
| \_autofill             | `&:autofill`                                                                                     |
| \_inRange              | `&:is(:in-range, [data-in-range])`                                                               |
| \_outOfRange           | `&:is(:out-of-range, [data-outside-range])`                                                      |
| \_placeholder          | `&::placeholder, &[data-placeholder]`                                                            |
| \_placeholderShown     | `&:is(:placeholder-shown, [data-placeholder-shown])`                                             |
| \_pressed              | `&:is([aria-pressed=true], [data-pressed])`                                                      |
| \_selected             | `&:is([aria-selected=true], [data-selected])`                                                    |
| \_grabbed              | `&:is([aria-grabbed=true], [data-grabbed])`                                                      |
| \_underValue           | `&[data-state=under-value]`                                                                      |
| \_overValue            | `&[data-state=over-value]`                                                                       |
| \_atValue              | `&[data-state=at-value]`                                                                         |
| \_default              | `&:default`                                                                                      |
| \_optional             | `&:optional`                                                                                     |
| \_open                 | `&:is([open], [data-open], [data-state="open"], :popover-open)`                                  |
| \_closed               | `&:is([closed], [data-closed], [data-state="closed"])`                                           |
| \_fullscreen           | `&:is(:fullscreen, [data-fullscreen])`                                                           |
| \_loading              | `&:is([data-loading], [aria-busy=true])`                                                         |
| \_hidden               | `&:is([hidden], [data-hidden])`                                                                  |
| \_current              | `&:is([aria-current=true], [data-current])`                                                      |
| \_currentPage          | `&[aria-current=page]`                                                                           |
| \_currentStep          | `&[aria-current=step]`                                                                           |
| \_today                | `&[data-today]`                                                                                  |
| \_unavailable          | `&[data-unavailable]`                                                                            |
| \_rangeStart           | `&[data-range-start]`                                                                            |
| \_rangeEnd             | `&[data-range-end]`                                                                              |
| \_now                  | `&[data-now]`                                                                                    |
| \_topmost              | `&[data-topmost]`                                                                                |
| \_motionReduce         | `@media (prefers-reduced-motion: reduce)`                                                        |
| \_motionSafe           | `@media (prefers-reduced-motion: no-preference)`                                                 |
| \_print                | `@media print`                                                                                   |
| \_landscape            | `@media (orientation: landscape)`                                                                |
| \_portrait             | `@media (orientation: portrait)`                                                                 |
| \_dark                 | `.dark &`                                                                                        |
| \_light                | `.light &`                                                                                       |
| \_osDark               | `@media (prefers-color-scheme: dark)`                                                            |
| \_osLight              | `@media (prefers-color-scheme: light)`                                                           |
| \_highContrast         | `@media (forced-colors: active)`                                                                 |
| \_lessContrast         | `@media (prefers-contrast: less)`                                                                |
| \_moreContrast         | `@media (prefers-contrast: more)`                                                                |
| \_ltr                  | `:where([dir=ltr], :dir(ltr)) &`                                                                 |
| \_rtl                  | `:where([dir=rtl], :dir(rtl)) &`                                                                 |
| \_scrollbar            | `&::-webkit-scrollbar`                                                                           |
| \_scrollbarThumb       | `&::-webkit-scrollbar-thumb`                                                                     |
| \_scrollbarTrack       | `&::-webkit-scrollbar-track`                                                                     |
| \_horizontal           | `&[data-orientation=horizontal]`                                                                 |
| \_vertical             | `&[data-orientation=vertical]`                                                                   |
| \_icon                 | `& :where(svg)`                                                                                  |
| \_starting             | `@starting-style`                                                                                |
| \_noscript             | `@media (scripting: none)`                                                                       |
| \_invertedColors       | `@media (inverted-colors: inverted)`                                                             |

## Custom conditions

Bamboo lets you create your own conditions, so you're not limited to the ones in the default preset. Learn more about
customizing conditions [here](/docs/customization/conditions).
