Open-Source Wikis

/

Solid

/

Packages

/

solid-element

solidjs/solid

solid-element

Active contributors: Ryan Carniato

solid-element wraps a Solid component as a standards-based Custom Element. It uses component-register for the registration mechanics and Solid's reactive primitives for the inside of the element.

Purpose

Custom Elements give you a globally-named, framework-agnostic component that any HTML page or any other UI framework can mount. solid-element lets you ship Solid-built components in that shape: declare default props, write the body as a Solid render function, and customElement("my-tag", ...) registers it.

The package is small (under 100 lines of source) and intentionally so — component-register does the registration heavy lifting.

Directory layout

packages/solid-element/
├── package.json                # version, deps, peerDep on solid-js
├── README.md                   # full usage docs
├── CHANGELOG.md
├── sample.jsx                  # working example component
├── tsconfig.json
└── src/
    └── index.ts                # customElement, withSolid, hot, getCurrentElement, noShadowDOM

Key abstractions

Symbol Source Description
customElement(tag, ComponentType) packages/solid-element/src/index.ts Two-arg overload: register without default props.
customElement(tag, propsDefinition, ComponentType) packages/solid-element/src/index.ts Three-arg overload: register with explicit default-prop values.
withSolid(ComponentType) packages/solid-element/src/index.ts Mixin that you can compose(register(tag), withSolid)(ComponentType) instead of using customElement directly.
hot, getCurrentElement, noShadowDOM re-exports from component-register Hot module reload helper, current-element accessor (for use inside the component body), and shadow-DOM opt-out.
ComponentType<T> (alias) packages/solid-element/src/index.ts Re-export of component-register's component type.

How it works

graph TD
    Register["register(tag, defaults)<br>(component-register)"]
    WithSolid["withSolid(Component)<br>(packages/solid-element)"]
    Element["Class extends HTMLElement"]
    Connect["connectedCallback()"]
    Root["createRoot(dispose => ...)"]
    Props["createProps(rawProps)<br>signal-backed proxy"]
    Insert["insert(renderRoot, comp)"]
    Disconnect["addReleaseCallback(...)"]

    Register -->|wraps| WithSolid
    WithSolid --> Element
    Element --> Connect
    Connect --> Root
    Root --> Props
    Root --> Insert
    Connect --> Disconnect

The flow inside withSolid (packages/solid-element/src/index.ts):

  1. Per-prop signal allocation. createProps(raw) walks the keys of the default-prop bag and allocates a createSignal for each. The returned object has a get/set pair that wraps the signal accessor and setter, so props.someProp is a tracked read.
  2. Owner allocation. createRoot(dispose => ...) allocates a fresh reactive owner. lookupContext(element) walks the DOM up to find an _$owner (the marker solid-js/web adds to host elements that own a Solid tree) and threads it as the parent owner so context propagates across element boundaries.
  3. Property-changed bridge. element.addPropertyChangedCallback((key, val) => props[key] = val) is what makes the element's reactive props update when an outer page sets el.someProp = ....
  4. Release callback. element.addReleaseCallback(() => { renderRoot.textContent = ""; dispose(); }) clears the element's render root and disposes the Solid owner when the element disconnects.
  5. Mount. insert(element.renderRoot, comp) (from solid-js/web) renders the component's output into the element. The render root is either the element's shadow root (default) or its light children if noShadowDOM() was called.

customElement is a thin wrapper that picks between two overloads (with or without explicit prop defaults) and chains register(tag, props)(withSolid(ComponentType)).

Sample component

packages/solid-element/sample.jsx shows the canonical usage:

import { customElement } from 'solid-element';
import { createSignal } from 'solid-js';

customElement('my-counter', () => {
  const [count, setCount] = createSignal(0);
  return (
    <div>
      <button onClick={() => setCount(count() - 1)}>-</button>
      <span>{count}</span>
      <button onClick={() => setCount(count() + 1)}>+</button>
    </div>
  );
});

After this script runs, <my-counter></my-counter> works in any HTML document.

Key source files

File Purpose
packages/solid-element/src/index.ts The whole implementation: createProps, lookupContext, withSolid, customElement.
packages/solid-element/sample.jsx A runnable example in JSX form.
packages/solid-element/README.md Full usage docs, including the compose(register, withSolid) form for adding extra mixins.

Integration points

  • Depends on component-register (declared in packages/solid-element/package.json). component-register handles the actual customElements.define, the V1 standards, the property-changed callback infrastructure, and the optional ShadyCSS Polymer polyfill.
  • Depends on solid-js and solid-js/web (peerDep). Uses createRoot, createSignal from solid-js and insert from solid-js/web.
  • Built with tsc directly (packages/solid-element/package.json build script), not Rollup. The output goes to dist/.
  • Only pnpm run build is wired up — there are no in-package tests beyond what packages/test-integration/test-imports.mjs covers indirectly.

Entry points for modification

  • Adding a new shadow-mode option: the shadow vs light-DOM decision happens inside component-register (addReleaseCallback, renderRoot). New modes would land there first.
  • Hooking a new lifecycle: withSolid already wires connect (createRoot) and disconnect (addReleaseCallback). To hook adoption or attribute changes, add a Custom Element callback to the register(tag, props) chain via compose(register, ...mixins, withSolid).
  • Improving context propagation: lookupContext(element) is the algorithm that walks parents to find an _$owner. Edge cases around assigned slots, slot retargeting, and shadow boundaries are intentionally tricky — review changes against packages/solid/web/test/ (which exercises the host marker behaviour from the solid-js/web side).

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

solid-element – Solid wiki | Factory