The + / - buttons are rendered inside the component and
handled there through on_action + data-act: the adapter
binds a click listener to the shadow root, which survives re-rendering,
and the value stays within 0..max. The page-side buttons next to it
drive the same state from outside with el.setAttribute("count", n).
Either path re-renders the component and notifies the page through an event declared
as crossing (bubbles + composed). An invalid value is not an error; it
falls back to the default.
The page gives .pill and span aggressive styles, yet the
.pill inside the component is untouched: shadow DOM blocks them.
The markup still shows up as literal text: the component escapes its text nodes and never executes user input as tags.
The contrast is visible: the component's .pill stays light grey (its
selector is rewritten to x-panel .pill, which outranks the page's
.pill), while the page's .pill is still pink.
The mechanism differs, and so do the limits: shadow DOM isolates
structurally, so page rules cannot get in at all, while light DOM isolation
only raises specificity with a prefix. This page's
span { letter-spacing: .3em } still reaches the component (its
<span> is spaced out too), and a page rule with
!important overrides the component. Light DOM suits SSR output, or
hosts that want their styles to bleed in.