Box Shadows for WordPress Blocks: Moving From style.css to theme.json

With WordPress 6.1 and the Twenty Twenty-Three default theme, the platform is pushing block theme styling into theme.json. While this configuration file handles a growing number of CSS properties, it doesn't cover everything—perspective-origin, for instance, remains unsupported. Fortunately, box-shadow is one of the properties that made the cut, even if that addition flew under the radar amid bigger layout and spacing announcements in the release.

Setting a shadow on a specific block, such as the Featured Image block, requires defining the style under the block's entry in settings.block.featuredImage:

{
  "version": 2,
  "settings": {},
  // etc.
  "styles": {
    "blocks" :{
      "core/post-featured-image": {
        "shadow": "10px 10px 5px 0px rgba(0, 0, 0, 0.66)"
      }
    }
  }
}

Notably, the newer space-separated color syntax (rgb(0 0 0 / 0.66)) doesn't work in this context—only the comma-based format is accepted at present. That may change with future updates or community contributions.

Targeting Buttons and Their States

Shadows aren't limited to whole blocks. You can apply them globally to a block element like a button—which is itself a block but can also live inside other blocks—by adding the style to styles.blocks.button:

{
  "version": 2,
  "settings": {},
  // etc.
  "styles": {
    "elements": {
      "button": {
        "shadow": "10px 10px 5px 0px rgba(0,0,0,0.66)"
      }
    }
  }
}

WordPress 6.1 also introduced support for pseudo-classes in theme.json, so interactive states such as :hover, :focus, :active, and :visited can be styled for certain elements. Adding a hover shadow to buttons works by placing the rule under styles.blocks.button.:hover:

{
  "version": 2,
  "settings": {},
  // etc.
  "styles": {
    "elements": {
      "button": {
        ":hover": {
          "shadow": "10px 10px 5px 0px rgba(0,0,0,0.66)"
        }
      }
    }
  }
}

Child themes can override these styles. The example below completely replaces the button styles from Twenty Twenty-Three:

View full code
{
  "version": 2,
  "settings": {},
  // etc.
  "styles": {
    "elements": {
      "button": {
        "border": {
          "radius": "0"
        },
        "color": {
          "background": "var(--wp--preset--color--tertiary)",
          "text": "var(--wp--preset--color--contrast)"
        },
        "outline": {
          "offset": "3px",
          "width": "3px",
          "style": "dashed",
          "color": "red"
        },
        "typography": {
          "fontSize": "var(--wp--preset--font-size--medium)"
        },
        "shadow": "5px 5px 5px 0px rgba(9, 30, 66, 0.25), 5px 5px 5px 1px rgba(9, 30, 66, 0.08)",
        ":hover": {
          "color": {
            "background": "var(--wp--preset--color--contrast)",
            "text": "var(--wp--preset--color--base)"
          },
          "outline": {
            "offset": "3px",
            "width": "3px",
            "style": "solid",
            "color": "blue"
          }
        },
        ":focus": {
          "color": {
            "background": "var(--wp--preset--color--contrast)",
            "text": "var(--wp--preset--color--base)"
          }
        },
        ":active": {
          "color": {
            "background": "var(--wp--preset--color--secondary)",
            "text": "var(--wp--preset--color--base)"
          }
        }
      }
    }
  }
}

The rendered result looks like this:

Showing two red buttons with box shadows.
The button’s natural state (left) and it’s hovered state (right)

Alternative Approach: Custom Properties

The Pixl theme demonstrates a different strategy: defining a custom value under settings.custom.shadow and referencing it later when styling elements.

{
  "version": 2,
  "settings": {
    // etc. 
    "custom": {
      // etc.
      "shadow": "5px 5px 0px -2px var(--wp--preset--color--background), 5px 5px var(--wp--preset--color--foreground)"
    },
    // etc.
  }
}

That custom property is then applied to a button element further down in the file:

{
  "version": 2,
  "settings": {
    // etc.
  },
  "styles": {
    "elements": {
      "button": {
        // etc.
        "shadow": "var(--wp--custom--shadow) !important",
        // etc.
        ":active": {
          // etc.
          "shadow": "2px 2px var(--wp--preset--color--primary) !important"
        }
      },
    // etc.
  }
}

The !important declaration here likely aims to override styles coming from the Global Styles UI, which tends to carry higher specificity. For more background, the topic was discussed in Pull Request #34689, which notes the issue was addressed in WordPress 5.9.

Beyond Shadows: Outline Support

box-shadow isn't the only property to gain theme.json support in WordPress 6.1. The CSS outline property works similarly and applies to buttons and their interactive states. A GitHub PR offers a solid example, and real-world usage can be seen in themes like Loudness, Block Canvas, and Blockbase.

"elements": {
  "button": {
    "outline": {
      "offset": "3px",
      "width": "3px",
      "style": "dashed",
      "color": "red"
    },
    ":hover": {
      "outline": {
        "offset": "3px",
        "width": "3px",
        "style": "solid",
        "color": "blue"
      }
    }
  }
}

Resources and Further Reading