Skip to content

Static Hoists

Litsx uses static name = ... for static component metadata that belongs to the generated class rather than to render-time execution.

That means:

  • static styles = ... attaches static stylesheet metadata
  • static properties = ... attaches Lit property metadata
  • any other direct static name = ... can attach additional static class metadata when Lit, Litsx, or your own runtime code knows how to consume it

This is authored syntax, not a runtime import.

Mental Model

Treat static name = ... as a compile-time static hoist.

The transform lowers each hoist to a memoized static getter on the generated class:

  • the getter resolves once per component class
  • object and array values keep stable identity

In practice, the authored form:

tsx
static styles = `
  :host {
    display: block;
  }
`;

becomes a generated class getter, not a runtime statement inside render().

That distinction matters: a hoist is still authored inside the component function, but semantically it belongs to the generated class shape.

Accepted Shapes

static name = ... accepts one direct static value.

Examples:

tsx
static styles = `
  :host {
    display: block;
  }
`;

static properties = {
  active: { reflect: true },
};

static shadowRootOptions = {
  delegatesFocus: true,
};

static lightDom = true;

Static Method Exposure

static expose = ... is the exception to the getter model. It lowers to real static class methods rather than to a memoized getter.

Do not confuse that with useExpose. useExpose(...) publishes an instance handle through a ref. static expose = ... defines class-level static methods that other components can call imperatively.

static expose = ... keeps the same authored shape as other hoists:

  • pass an object literal to define methods directly

For example:

tsx
static expose = {
  canHandle(type) {
    return type === "dialog";
  },
};

static expose = ... is useful when a child component owns domain logic that a parent wants to call imperatively at the class level.

Light DOM

static lightDom = true opts a component out of the default shadow root and lowers to:

js
createRenderRoot() {
  return this;
}

static lightDom = true is incompatible with:

  • static shadowRootOptions = ...

Imported LitSX components used from a static lightDom = true component keep their base custom-element tag and resolve through a contextual light DOM registry at runtime.

In the example below:

  • the component opts into static lightDom = true
  • the demo stays in a single playground file
  • two light DOM hosts both render the same <profile-chip> tag
  • each host resolves that tag to a different implementation through static elements

Static Expose Example

Use static expose = ... when the parent should call static class-level behavior such as registries, presets, classification, or factory methods.

Use useExpose when the parent needs an imperative handle for one rendered instance, such as focus(), open(), or reset().

In the example below:

  • ProfileChip exposes static methods with static expose = ...
  • the parent calls those methods to ask the child for the next preset and tone
  • the rendered child still receives normal props, but the imperative coordination lives on the child class API

Top-Level Only

Static hoists must appear as top-level statements in the component body.

Valid:

tsx
export function Card() {
  static styles = `
    :host {
      display: block;
    }
  `;

  return <article>ready</article>;
}

Invalid:

tsx
export function Card({ active }) {
  if (active) {
    static styles = `:host { display: block; }`;
  }

  return <article>ready</article>;
}

The second form is rejected because hoists belong to the component type, not to control flow inside a render path.

Relationship To Runtime

The generated getters rely on runtime support from @litsx/core/elements.

That module exists to support compiler output. It is runtime support code, not part of the normal authored surface you import in application code.