Directives#

Attach reusable lifecycle behavior to a single element with the use:* prop syntax — including the built-in show, which toggles visibility while keeping the element mounted.

A directive is a small object of lifecycle hooks that runs against the native element it's attached to. SignalX's runtime-core directive system is wired into the Lynx renderer, so directives work on the background-thread ShadowElement — no DOM required.

The use:* syntax#

Attach a directive with a use:<name> prop:

TSX
import { signal } from '@sigx/lynx';

const isOpen = signal(true);

// Shorthand — `name` resolves a registered directive (built-in, then app):
<view use:show={isOpen.value}>Content</view>;

There are two ways to give a directive its definition and value:

TSX
import { show } from '@sigx/lynx';

// Shorthand: use:name={value} — resolves `name` from the registry.
<view use:show={isOpen.value} />;

// Explicit tuple: use:name={[definition, value]} — pass the definition directly.
<view use:show={[show, isOpen.value]} />;

The shorthand resolves name first from the built-in registry (where show lives), then from per-app app.directive() registrations. The explicit tuple skips resolution by passing the definition itself — handy for custom directives you import locally.

Lifecycle hooks#

A directive definition has four optional hooks, all receiving the host element and a binding { value, oldValue }:

HookWhen
createdAfter the element is created, on the directive's first sight.
mountedAfter the element is inserted into the tree.
updatedWhen the bound value changes (oldValue is the previous value).
unmountedBefore the element is removed — or when the use:* prop is removed from a still-mounted element.

Defining a directive#

Use defineDirective (re-exported from @sigx/lynx). The LynxDirective<T> helper types the definition against Lynx's ShadowElement host:

TSX
import { defineDirective, type LynxDirective } from '@sigx/lynx';

const autofocus: LynxDirective<boolean> = defineDirective({
  mounted(el, { value }) {
    if (value) {
      // focus the element…
    }
  },
});

To type the use:autofocus attribute on a use:* prop, the DirectiveAttribute<T> helper describes one directive attribute in a line.

Registering directives#

A directive must be resolvable for the shorthand use:name={value} form to work. Two options:

  • GloballyregisterBuiltInDirective(name, def) makes a directive available app-wide. This is the seam for directive packs.

    TSX
    import { registerBuiltInDirective } from '@sigx/lynx';
    registerBuiltInDirective('autofocus', autofocus);
  • Per appapp.directive(name, def) registers on a single app context. Preferred when an app context exists.

    TSX
    app.directive('autofocus', autofocus);

You can always skip registration by using the explicit tuple form use:name={[def, value]} with an imported definition.

The show directive#

show ships built in and is registered with the platform automatically, so use:show works out of the box.

TSX
import { signal } from '@sigx/lynx';

const isOpen = signal(false);

<view use:show={isOpen.value}>Content that stays mounted</view>;

use:show toggles the element's visibility via display while keeping it mounted — a single style op instead of an unmount/remount. Because the element stays alive, it preserves native state: input focus and value, scroll position, and any expensive subtree.

Tradeoff: a hidden subtree stays mounted as live native views, so it still costs memory. Prefer conditional rendering ({cond && <view/>}, which unmounts/remounts) for large branches that are rarely shown; reach for use:show when toggling is frequent or you want to keep element state.

show here is a Lynx-native implementation — it toggles a Lynx style op directly, distinct from the runtime-dom show on the web. It is mobile-only; there is no SSR for it.

See also#