Skip to content

feat(core): support scoped custom element registries - #6843

Draft
rubencarvalho wants to merge 1 commit into
mainfrom
ruben/scoped-registries
Draft

rubencarvalho wants to merge 1 commit into
mainfrom
ruben/scoped-registries

Conversation

@rubencarvalho

Copy link
Copy Markdown
Contributor

Description

Draft: experimental support for scoped custom element registries in gen2. Independently built apps on one page can each use their own version of Spectrum Web Components with the same swc-* tag names. Pages that use one version keep today's imports and behavior.

Core (@adobe/spectrum-wc-core)

  • SpectrumElement has a static define(registry = customElements). It defines the class's dependencies first, then the class under its own static tagName.
  • SpectrumElement uses the new ScopedElementMixin. An element created in a scoped registry attaches its shadow root with that registry and clones Lit templates with it. Elements created in the global registry render exactly as before.
  • defineElement(name, constructor, registry?) takes an optional registry and does nothing when the same constructor is already defined. Shared dependencies, such as <swc-ui-icon>, can then be defined from several components.
  • createScopedRegistry(version) creates the package registry, publishes it at window.__swc.scopedRegistries, and throws a clear error when the browser lacks support. The first copy of a version keeps the index entry. Later copies warn in development.

SWC (@adobe/spectrum-wc)

  • New entry @adobe/spectrum-wc/registry: registry, version, ScopedElementMixin, scopedRenderOptions.
  • Every component and pattern class declares static tagName. Each swc-*.ts registration file is now Component.define().
  • Class modules no longer side-effect import other swc-*.js files. Composite classes list the classes they render in static dependencies (Close Button, Accordion Item, Color Handle, and the AI toolkit patterns).
  • An ESLint no-restricted-imports rule keeps swc-*.js imports out of class modules. Workflow icon element modules get their own message that points to the Icon_* SVG functions.
  • scripts/generate-versions.js also writes gen2/packages/swc/registry/version.ts, so the index uses the @adobe/spectrum-wc version. Core's version can differ, because changesets links the gen2 package versions rather than fixing them.

Docs and tooling

  • New guide: Guides / Scoped registries.
  • VITEST_BROWSER_CHANNEL=chrome runs the Vitest suite in system Chrome. Playwright 1.53 bundles Chromium 138, which predates native support (Chrome 146).

Breaking (beta): Importing a class, for example CloseButton from @adobe/spectrum-wc/close-button, no longer defines <swc-ui-icon> globally. Consumers that call customElements.define() on a class themselves should call CloseButton.define() or import the swc-*.js file instead. The changeset documents this.

Not in this PR:

  • Workflow icons from @adobe/spectrum-wc-icons. Their generator writes the class and its global definition in one module. The guide points scoped apps to the Icon_* SVG functions inside <swc-icon>.
  • Emitting tokens at :root, :host, so one tokens.css also works inside an app's shadow root.

Motivation and context

Today every gen2 component registers in the single global registry, so a page can run one version of <swc-button>. Hosts that compose independently versioned apps need each app's SWC version, including the elements components render internally, to resolve from that app's registry.

Scoped registries ship in Chrome and Edge 146+ and Safari 26+. Firefox has them in Nightly. A polyfill for the standard API is in review in webcomponents/polyfills#668. Production use waits for that release. Lit support is in review in lit/lit#5246 and lit/lit#5260. ScopedElementMixin and scopedRenderOptions bridge that gap and will be deprecated when Lit releases it.

Related issue(s)

  • Jira ticket number to be added before review.

Screenshots (if appropriate)

Not applicable. No visual changes.


Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed at the Accessibility Practices for this feature, see: Aria Practices
  • I have added automated tests to cover my changes.
  • I have included a well-written changeset if my change needs to be published.
  • I have included updated documentation if my change required it.

Validation

Run from the repository root unless noted:

  • yarn type-check:gen2: passed.
  • yarn eslint <changed files>: passed, 0 errors.
  • yarn prettier --check <changed files>: passed.
  • yarn lint:ai: passed.
  • yarn lint:docs-pages: passed.
  • node scripts/generate-versions.js --check: passed.
  • yarn build in gen2/packages/core and in gen2/packages/swc: passed. dist/registry/index.js and dist/registry/index.d.ts are emitted.
  • yarn vitest --run --project storybook in gen2/packages/swc, bundled Chromium 138: 1254 passed. One earlier run had 3 timing failures in menu.test.ts and tabs.test.ts. They passed in isolation and on the rerun.
  • VITEST_BROWSER_CHANNEL=chrome yarn vitest --run --project storybook in gen2/packages/swc, Chrome 155: 1254 passed. This includes the native scoped-registry tests.
  • Mutation checks: the new tests fail if attachShadow ignores the host registry, if Close Button drops dependencies, or if a class module imports swc-ui-icon.js again.

Reviewer's checklist

  • Includes a Github Issue with appropriate flag or Jira ticket number without a link
  • Includes thoughtfully written changeset if changes suggested include patch, minor, or major features
  • Automated tests cover all use cases and follow best practices for writing
  • Validated on all supported browsers
  • All VRTs are approved before the author can update Golden Hash

Manual review test cases

  • Global registration is unchanged

    1. Run Storybook (yarn storybook in gen2/packages/swc).
    2. Open Close Button, Accordion, Color Handle, and Prompt Field.
    3. Expect each component, including its internal icons and loupe, to render and behave as on main.
  • Scoped registries work natively

    1. In gen2/packages/swc, run VITEST_BROWSER_CHANNEL=chrome yarn vitest --run --project storybook scoped-registry with Chrome 146+ installed.
    2. Expect 9 tests to pass, including one class rendering different child definitions from two registries.
  • Unsupported browsers fail loudly

    1. In gen2/packages/swc, run yarn vitest --run --project storybook scoped-registry. This uses the bundled Chromium 138.
    2. Expect the tests to pass. The unsupported-browser story asserts that createScopedRegistry() throws a clear error.
  • The lint rule blocks global imports in class modules

    1. Add import '../ui-icons/swc-ui-icon.js'; to gen2/packages/swc/components/close-button/CloseButton.ts.
    2. Run yarn eslint gen2/packages/swc/components/close-button/CloseButton.ts.
    3. Expect a no-restricted-imports error that points to static dependencies.
  • Guide reads correctly

    1. Open Storybook, then Guides / Scoped registries.
    2. Expect the page to render, with code samples that match the API in this PR.

Device review

  • Did it pass in Desktop?
  • Did it pass in (emulated) Mobile?
  • Did it pass in (emulated) iPad?

Accessibility testing checklist

This PR changes registration only. Markup, ARIA, and focus handling don't change. Spot-check the composite components whose internal elements now register through dependencies.

  • Keyboard (required — document steps below) — What to test for: Focus order is logical; Tab reaches the component and all interactive descendants; Enter/Space activate where appropriate; arrow keys work for tabs, menus, sliders, etc.; no focus traps; Escape dismisses when applicable; focus indicator is visible.

    1. Open the Close Button and Accordion stories in Storybook.
    2. Press Tab to reach each control, and activate it with Enter and Space.
    3. Expect the same focus order, activation, and visible focus indicator as on main.
  • Screen reader (required — document steps below) — What to test for: Role and name are announced correctly; state changes (e.g. expanded, selected) are announced; labels and relationships are clear; no unnecessary or duplicate announcements.

    1. With VoiceOver on, move to the Close Button and to an Accordion item.
    2. Expect "Close, button" and the item's heading, button role, and expanded state, as on main. The UI icons stay hidden from assistive technology.

Let independently built apps on one page each use their own version of
gen2 components with the same swc-* tag names.

- add static define(registry), tagName, and dependencies to SpectrumElement
- create shadow roots and clone Lit templates with the host's registry
- add the @adobe/spectrum-wc/registry entry with the package registry,
  ScopedElementMixin, and scopedRenderOptions
- declare rendered elements in static dependencies instead of importing
  swc-*.js files from class modules, and lint against those imports
- generate the @adobe/spectrum-wc version that keys the registry index
- add a scoped registries guide, tests, and a changeset

BREAKING CHANGE: importing a component class no longer defines the
elements it renders in the global registry. Call Component.define() or
import the component's swc-*.js file.
@rubencarvalho rubencarvalho added Feature New feature or request Status:WIP PR is a work in progress or draft API Issues or PRs involving API changes Breaking gen2 These issues or PRs map to our 2nd generation work to modernizing infrastructure. labels Oct 21, 2026
@changeset-bot

changeset-bot Bot commented Oct 21, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: e26e961

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

📚 Branch Preview Links

🔍 Gen1 Visual Regression Test Results

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

Deployed to Azure Blob Storage: pr-6843

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

@rubencarvalho rubencarvalho removed Feature New feature or request Breaking labels Oct 23, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API Issues or PRs involving API changes gen2 These issues or PRs map to our 2nd generation work to modernizing infrastructure. Status:WIP PR is a work in progress or draft

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant