1. Question and context
A server-rendered admin page has several components using generic .title and .button classes. A new marketing card changes button spacing and heading colors on old pages. Design a gradual migration with @scope while preserving server-rendered first paint, theme variables, and older-browser support. Components remain in one document DOM and do not use Shadow DOM.
2. What the interviewer evaluates
- Understanding that
@scopelimits selector matching but does not create Shadow DOM isolation. - Explaining scoping roots, optional scope limits, nested scopes, and cascade order rather than memorizing syntax.
- Separating component state, theme variables, global resets, and third-party styles.
- Designing capability detection, fallback behavior, visual regression, and migration metrics.
3. Questions to clarify first
- Which browsers must work, and must an unsupported browser be pixel-identical or use a legacy CSS path?
- Do components share DOM and inherit theme variables, or do they need true DOM and event isolation?
- Are styles loaded by page, component, or third-party package, and how long will old and new rules coexist?
- Should a scope cover all descendants of the root, or stop at a nested subtree?
4. A 30-second answer
I would treat @scope as selector-range control, not Shadow DOM. Give each component a stable root class and keep its rules inside that scope; use to when matching must stop at a nested boundary. Keep theme variables on an explicit page or component root and express state with local classes or attributes. Supported browsers use scoped rules; unsupported ones use a namespaced build from the same semantic rules. Migrate one component at a time with cascade layers, visual regression, and rule-hit metrics so old and new selectors do not silently fight.
5. Step-by-step answer
Step 1: Separate the isolation goal
Determine whether the problem is selector collision, inheritance pollution, or a DOM/event security boundary. @scope addresses selector reach while still allowing variable inheritance and script access to the same document nodes. Choose Shadow DOM when DOM, styles, and events must be isolated; choose CSS Modules when build-time modular class names are the main goal.
Step 2: Define the scope root and limit
Use the component root as the scope root and keep internal selectors readable:
@scope (.profile-card) {
.title { color: var(--card-title); }
.button { padding-inline: 0.75rem; }
}To exclude a nested third-party editor inside .profile-card, use @scope (.profile-card) to (.editor) { ... }. The limit only says where matching stops; it does not clone nodes or block custom-property inheritance. Document roots and limits for nested scopes so one element is not accidentally owned by several components.
Step 3: Handle cascade, state, and themes
Scoping does not replace the cascade. Put resets, component defaults, and overrides in explicit @layers; use :where() when a rule should remain easy to override instead of increasing selector specificity. Express state on the component root with [data-state] or local classes and assign those rules to a defined layer. Theme variables may come from the page root or be overridden at the component root, but internal variables should not accidentally become a global contract.
Step 4: Design compatibility and migration
Use @supports selector(:scope) or a browser-target matrix to choose a path, then verify actual target browsers. A legacy build can expand the same rules into namespaced selectors such as .profile-card .title; both paths must come from one source rule set. Migrate component by component, compare screenshots while old and new rules coexist, and remove the old global selector only after the new path is observable. Do not wrap the entire application in one giant scope.
Step 5: Verify boundaries and rollback
Test same-name classes in nested components, the scope root itself, elements outside a to boundary, theme changes, combined states, dynamically inserted nodes, and third-party subtrees. Track scoped-rule coverage, browser capability distribution, visual diffs, and unmatched rules in the build output. If regressions increase, roll back the component or disable the enhanced path; do not hide the boundary error by adding more specificity.
6. Model answer
I would first decide whether we need selector boundaries or full DOM isolation. For shared document DOM,@scope (.profile-card)keeps internal selectors local andtocan stop matching at a nested editor; it does not isolate events or prevent custom-property inheritance like Shadow DOM. I would place reset, defaults, and state overrides in explicit cascade layers and define theme variables only at page or component roots. Unsupported browsers would use a namespaced build from the same rules. Migration would be component-by-component with screenshot diffs, capability distribution, and unmatched-rule metrics. A boundary regression rolls back that component instead of escalating selector specificity.
7. Common mistakes
- Treating
@scopeas Shadow DOM: DOM, events, and variables remain shared; choose the primitive that matches the isolation goal. - Changing selectors but ignoring layers: An old rule can still win in a higher layer; define layers and state precedence together.
- Wrapping the whole application in one scope: Component boundaries gain little and rollback becomes difficult; split by component root.
- Letting old browsers ignore the rule: Critical styles disappear; ship a namespaced legacy path and test a capability matrix.
- Fixing regressions with higher specificity: Coupling and override cost grow; remodel the root, scope limit, and cascade layer.
8. Follow-up questions and responses
Follow-up 1: Can @scope stop a child component inheriting theme variables?
No. It mainly limits selector matching; custom properties still follow CSS inheritance. Override variables on the child root or use Shadow DOM when a separate variable environment is required.
Follow-up 2: What wins when nested scopes match the same element?
The normal cascade and scope-related ordering still apply; “the inner scope always wins” is not a safe rule. Put scopes in defined layers and inspect a minimal reproduction in browser DevTools.
Follow-up 3: How do unsupported browsers remain consistent?
Generate namespaced selectors or stable CSS-Module classes from the same source rules, preserving variables, state, and layer semantics. Runtime detection selects an implementation path; business components should not maintain two state machines.