:root, and every component inherits them already resolved. A --kui-* property redeclared on a wrapping <div> therefore reaches nothing on its own.
ThemeScope resolves them again on an element of its own. An override handed to the scope’s theme, or declared on it through className or style, or on any element around it, reaches every component inside, and the rest of the page keeps its own tokens:
KuiTheme is the list tokens.css declares, as a type: every property is optional, takes a value in CSS syntax, and shows its default in an editor. A class carrying the same declarations does the same job, for an app that would rather keep them in a stylesheet.
theme holds one value per property for both schemes, so a theme with a light and a dark value is two maps, and the scope is handed the one for the scheme in use.
Pinning a scheme on the scope
scheme pins the scope to 'light' or 'dark', whatever the OS asks for. Left unset, the scope keeps the scheme around it, so a scope inside a dark page stays dark.
Overlays come along
React Aria portals a popover, a sheet, a tooltip or a snackbar to the end of<body>, outside any wrapper. One opened inside a ThemeScope portals into the scope instead, and takes its tokens with it.
The overlays’ container is one more child of the scope’s element, after everything the scope renders. It takes up no room, but selectors that count children count it: inside the scope, the last element you render is not :last-child, a lone child is not :only-child, and an empty scope is not :empty. Key a rule like that on a class of your own, or put the scope around the element whose children it counts.
The class form
themeScope is the same thing as a class name, for an element you render yourself. Overlays opened under it still portal to the body and keep the page’s tokens, since nothing tells them otherwise.
