Skip to content

Conditions and breakpoints

Edit this page

A condition applies styles in a given state: on hover, in dark mode, when an input is invalid... A breakpoint applies them from a given screen width. Both come from Panda CSS, with the same names and the same selectors.

Writing a condition

In css(), a condition starts with an underscore. There are two ways to write it. Both give the same classes:

1
2
3
4
5
{# a hash of styles under the condition #}
<a class="{{ css({ color: 'fg', _hover: { color: 'primary', bg: 'gray.50' } }) }}">

{# a conditional value, with "base" for the default #}
<a class="{{ css({ color: { base: 'fg', _hover: 'primary' } }) }}">

The first form groups several properties under one condition. The second one keeps all the values of one property together. base is only valid inside a conditional value.

Conditions can be nested. The class names show the chain:

1
<button class="{{ css({ _hover: { _dark: { bg: 'gray.900' } } }) }}">

This gives hover:dark:bg_gray.900, which applies on hover in dark mode.

Default conditions

The bundle ships every condition of Panda CSS. The most common ones:

Condition Applies when
_hover &:is(:hover, [data-hover])
_focus &:is(:focus, [data-focus])
_focusVisible &:is(:focus-visible, [data-focus-visible])
_active &:is(:active, [data-active])
_disabled &:is(:disabled, [disabled], [data-disabled], [aria-disabled=true])
_checked &:is(:checked, [data-checked], [aria-checked=true], [data-state="checked"])
_invalid &:is(:invalid, [data-invalid], [aria-invalid=true])
_expanded &:is([aria-expanded=true], [data-expanded], [data-state="expanded"])
_open &:is([open], [data-open], [data-state="open"], :popover-open)
_placeholder &::placeholder, &[data-placeholder]
_before, _after &::before, &::after
_first, _last &:first-child, &:last-child
_even, _odd &:nth-child(even), &:nth-child(odd)
_dark, _light .dark &, .light &
_osDark @media (prefers-color-scheme: dark)
_motionReduce @media (prefers-reduced-motion: reduce)
_print @media print
_groupHover .group:is(:hover, [data-hover]) &
_peerFocus .peer:is(:focus, [data-focus]) ~ &

& stands for the element that has the class. The full list is in the Panda CSS documentation.

_groupHover and the other _group* conditions style an element when an ancestor with the group class is hovered, focused, and so on. The _peer* conditions do the same with a previous sibling that has the peer class:

1
2
3
<a href="#" class="group">
    <span class="{{ css({ color: 'fg', _groupHover: { color: 'primary' } }) }}">Docs</span>
</a>

Dark mode

By default, _dark applies inside an element that has the dark class, usually <html>. To follow the operating system preference instead, use _osDark. To use another selector, redefine the condition:

1
2
3
4
# config/packages/ux_css.yaml
ux_css:
    conditions:
        dark: '[data-theme=dark] &'

Semantic tokens follow the new selector too: their dark values are written under [data-theme=dark].

Your own conditions

conditions adds conditions, or replaces a default one with the same name. A condition is a selector that contains &, or an at-rule:

1
2
3
4
5
6
# config/packages/ux_css.yaml
ux_css:
    conditions:
        expanded: '&[aria-expanded=true]'
        sidebarOpen: 'body.sidebar-open &'
        supportsGrid: '@supports (display: grid)'

Declare the name without the underscore, and use it with one: _sidebarOpen: { ml: 'lg' }.

A condition without & that does not start with @ is refused when the container is built:

1
2
The "dark" condition must contain "&" or start with "@", "[data-theme=dark]"
given.

Breakpoints

Breakpoints are written without an underscore. They are mobile-first. A breakpoint applies from its minimum width. base applies below the first one.

1
2
3
4
5
{# a hash of styles under the breakpoint #}
<div class="{{ css({ p: 'sm', md: { p: 'lg' } }) }}">

{# a conditional value #}
<div class="{{ css({ p: { base: 'sm', md: 'lg' } }) }}">

Both give p_sm md:p_lg. The default breakpoints are those of Panda CSS:

Name Minimum width
sm 640px
md 768px
lg 1024px
xl 1280px
2xl 1536px

The media queries are written in rem: md gives @media screen and (min-width: 48rem).

Each breakpoint also comes with ranges: mdOnly applies from md up to lg, mdDown below md, and smToLg from sm up to lg.

An array is a short form for the breakpoints in order, starting at base: p: ['sm', 'md', 'lg'] means { base: 'sm', sm: 'md', md: 'lg' }.

breakpoints replaces the default list:

1
2
3
4
5
# config/packages/ux_css.yaml
ux_css:
    breakpoints:
        tablet: '48rem'
        desktop: '80rem'

A breakpoint cannot have the name of a CSS property or shorthand, since both are written as keys of the same hash: p or color is refused when the container is built.

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version