demo ยท v130

Part State Machine

Chrome 130 allows pseudo-classes like :hover, :focus, :disabled and pseudo-elements like ::before, ::after, ::placeholder after ::part(). Toggle the exported shadow parts below; the visuals come from host-page CSS rules, not inline styles.

Detecting CSS ::part() + pseudo support...

Live shadow component:

selector boundary

The new grammar accepts pseudo-classes and pseudo-elements after ::part(), but it still rejects re-targeting or tree-structural selectors after a part. These probes run in the current browser.

What Chrome 130 enables

::part(button):hover
Before: mirror hover with host attributes or JS listeners
Chrome 130: direct host stylesheet selector
Style hover state of a shadow part directly from the host document stylesheet.
::part(button):focus
Before: component had to own every focus treatment
Chrome 130: host can add focus ring
Host page can inject a custom focus indicator without changing the component internals.
::part(field)::placeholder
Before: host could expose the field part, but not chain ::placeholder
Chrome 130: host can style placeholder text
The placeholder pseudo-element can now be chained onto the exported input part.
::part(button)::before
Before: no generated content after the part selector
Chrome 130: generated content from host CSS
Add decorative generated content to a shadow part from outside the shadow tree.
::part(button):disabled
Before: component had to expose a custom disabled styling hook
Chrome 130: CSS-only disabled style
Apply dimmed appearance when a shadow button is semantically disabled.
::part(check):checked
Before: checked state styling needed component-owned CSS
Chrome 130: checked state from host CSS
Style a checked checkbox or radio inside shadow DOM from the host stylesheet.
/* Host-page CSS generated by the state toggles. */
part-state-machine[data-states~="hover"]::part(button),
part-state-machine::part(button):hover {
  background: color-mix(in srgb, var(--accent-blue) 22%, var(--bg-paper));
}

part-state-machine::part(button):disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

part-state-machine::part(check):checked {
  outline: 2px solid var(--accent-emerald);
}

part-state-machine[data-states~="checked"]::part(button)::before {
  content: "checked ";
}

see also