Theme
Version
GitHub PyPI Discord
On this page

Browser APIs

Citry adds a small browser API around its component boundaries, Alpine runtime, server events, and client-enabled i18n providers. This page covers the names Citry provides. For standard Alpine directives and magics, use the Alpine documentation.

Component JavaScript

$component

Register the JavaScript that belongs to one component class. Use $component inside Component.js or the file named by Component.js_file. Citry binds that registration to the Python component class, then calls its initializer for every live rendered instance.

The callback form accepts one initializer:

$component(({ els, data, scope }) => {
  console.log(data.name, scope.name);
  els[0].dataset.ready = "true";
});

The configuration form adds declared client props:

$component({
  props: {
    name: {
      type: String,
      required: true,
    },
  },
  init: ({ props, scope }) => {
    scope.name = props.name;
  },
});

A component class may register exactly one $component initializer. Citry seeds the component scope from js_data() first, then runs the initializer synchronously after the component boundary, parent initialization, and client props are ready. Do not return a Promise.

Return a function when the initializer creates something that must be cleaned up. Citry calls it before that instance initializes again and when the instance leaves the page:

$component(({ els }) => {
  const chart = createChart(els[0]);
  return () => chart.destroy();
});

The initializer receives these values:

NameValue
idThe current server render ID. A rerender may replace it.
elsA stable array containing the instance's current element roots. It is empty for a rootless component.
dataA fresh instance-local graph parsed from the JSON returned by js_data(), or null.
graphThe current ownership route and source metadata when the instance belongs to a client graph.
propsThe stable, reactive, top-level read-only values declared by the configuration form. The callback form receives an empty object.
scopeThe stable reactive object available to Alpine expressions inside this component. Its top-level server-data keys are seeded before init.
stateThe component's reactive Events State, or null when the component declares no Events.
i18nThe nearest client-enabled i18n service, or null outside such a provider.
effect(fn)Run a managed reactive effect. It returns an early-stop function and stops automatically before cleanup.
reactive(value)Turn an object or array into an Alpine reactive proxy.
provide(key, value)Provide a value to rendered descendants during synchronous initialization.
inject(key, default?)Read the nearest inherited client value. Missing values throw unless a default was supplied.
unprovide(key)Hide an inherited value from rendered descendants during synchronous initialization.
sendEvent(name, args?, opts?)Call one of this component's declared server events and return a Promise for its data result.
onEvent(name, callback)Listen for server-dispatched events targeting this instance and return an unsubscribe function.
loading(name?)Return whether any handler, or one named handler, is queued or running.
error(name?)Return the newest retained error, or the retained error for one named handler.

$component is optional when the template only needs server data in Alpine expressions. Returning {"count": 0} from js_data() makes count available to x-text="count" and @click="count++" directly. On a compatible rerender, Citry refreshes current seeded keys and removes old seeded keys that disappeared, while preserving unrelated fields added by the callback.

A prop declaration accepts type, required, and default. type may be a constructor or an array of constructors. required defaults to false. Use a factory for an object or array default so instances do not share one mutable value:

props: {
  filters: {
    type: Object,
    default: () => ({}),
  },
}

The Client interactivity page explains component boundaries, client props, slot scope, and rootless components. The Component JavaScript and CSS explains when component scripts load.

$component.i18n

The i18n value in a $component initializer is the same service exposed as $i18n to Alpine expressions. It is null unless the component is below a client-enabled <c-i18n> provider. Use it for browser-created destinations or other imperative code; use the $c-tr template binding for stable text and HTML attributes.

Alpine magics

Citry adds the following magics to Alpine expressions inside an active Citry component. The event-related magics act on the component instance that owns the expression. The context magics follow Citry's rendered ownership path, including slots and teleports.

$i18n

Read the nearest client-enabled i18n provider. Access outside one throws an error. The service has this public shape:

MemberMeaning
contextReadonly locale, fallback, direction, time-zone, and revision data.
statusReadonly provider loading state.
tr(message, values?, options?)Return loaded message text. Use { attr: "name" } for a Fluent attribute.
resolve(message, values?, options?)Return frozen { text, locale, direction, usedFallback } metadata.
formatNamed number, percent, currency, date, time, datetime, relative-time, list, and unit formatters.
parseStrict number and percent parsers. Each returns { input, state, value, error, valid }.
ensureMessages(messages)Load one public message ID or a list before a dynamic synchronous lookup.
switchLocale(locale)Atomically switch this provider subtree and return a committed or stale result.
subscribe(callback)Call back immediately and after context changes; returns an unsubscribe function.
bind(options)Keep a browser-created destination translated; returns refresh() and dispose().
<c-i18n tag="section" client>
  <output x-text="$i18n.tr('my-app-status')"></output>
  <button @click="$i18n.switchLocale('cs-CZ')">Čeőtina</button>
</c-i18n>

Ordinary server tr() output is plain HTML and does not react to switchLocale(). See Browser i18n for $c-tr, dynamic message loading, bind(), and the exact ownership rules.

$state

Read the component's reactive public Events State. Assigning a writable field queues that change for the component's next server call. A field excluded by State._public cannot be read, and a field excluded by State._model cannot be written.

<button @click="$state.count++">Add one</button>
<output x-text="$state.count"></output>

State travels through the browser and must be treated as client input. See Security.

$loading

Return whether this component has a queued or running server call. Pass an event name to check only that handler. An unknown handler name throws an error.

<button :disabled="$loading('save')">
  <span x-show="!$loading('save')">Save</span>
  <span x-show="$loading('save')">Saving...</span>
</button>

$error

Read retained handler errors as { status, code, message, fieldErrors? }, or null when there is no matching error. Call $error() for the newest retained error across this component's handlers. Pass a handler name to read only that handler. An unknown handler name throws an error.

<p
  x-show="$error('save')"
  x-text="$error('save')?.message"
></p>

A successful call clears only its own handler's error. A retry leaves that error visible while the new call is queued or running, then replaces it on failure or clears it on success. Reading an error does not clear it.

$sendEvent

Call a declared server event from an Alpine expression:

$sendEvent(name, args?, opts?)

The method returns a Promise. It resolves with the handler's data result and rejects with a structured event error. opts.timeout overrides the request timeout. opts.wait: false lets a call bypass the component's event queue.

<button
  @click="result = await $sendEvent('preview', { page: 2 })"
>
  Preview page 2
</button>

Only imperative calls receive a returned actions.Data value. A declarative @c-* binding starts the same handler but does not expose its Promise result. Return actions.Dispatch when browser code must observe a result from a declarative call.

$onEvent

Listen for server-dispatched events targeting this component instance:

const stop = $onEvent("cart:changed", (detail) => {
  console.log(detail);
});

The return value removes the listener. Use the onEvent member inside $component when the subscription should automatically share the component initializer's cleanup lifetime.

$provide

Provide one value to rendered descendants:

<section x-init="$provide('theme', { name: 'dark' })">
  <c-slot />
</section>

The key may be a non-empty string or a Symbol. Call $provide during synchronous directive initialization, normally from x-init. To change the value later, provide one reactive object and update its fields.

$inject

Read the nearest inherited value. Citry returns the exact value that was provided. A missing key throws unless you pass a default:

<output x-text="$inject('theme', 'system')"></output>

The helper is bound to the element where the magic is read. Its lookup follows Citry's rendered ownership path rather than relying only on physical DOM parents.

$unprovide

Hide an inherited value from rendered descendants:

<section x-init="$unprovide('theme')">
  <output x-text="$inject('theme', 'system')"></output>
</section>

Call $unprovide during synchronous directive initialization. A nearer $provide can establish the same key again for its own descendants.

See Provide and inject for shadowing, slot placement, reactive updates, and multi-placement behavior.

Page-wide APIs

Use these methods from page scripts and integrations rather than from one component's Alpine expressions.

Citry.alpine.beforeStart

Register an Alpine plugin before Citry starts its owned Alpine runtime:

Citry.alpine.beforeStart((Alpine) => {
  Alpine.plugin(myPlugin);
});

The callback receives Citry's pinned Alpine object. Calling beforeStart after startup has begun throws an error. Do not load a second Alpine build.

Citry.events.send

Call a server event on any interactive component instance:

Citry.events.send(target, name, args?, opts?)

target is a current render ID or an Element inside the target instance. The method returns the same kind of Promise as $sendEvent and the component initializer's sendEvent member.

Citry.events.on

Listen page-wide for a server-dispatched event:

const stop = Citry.events.on("cart:changed", (detail) => {
  updateHeader(detail);
});

The callback receives the event's detail. The returned function removes the listener. Unlike $onEvent, this listener is not limited to one component instance.

Citry.events.configure

Set page-wide defaults for later event calls:

Citry.events.configure({
  timeout: 45_000,
  url: "/citry/ext/events/",
});
OptionMeaning
csrfThe token source and request-header name. It accepts cookie, header, or a token string or function.
timeoutMilliseconds before a call rejects. The default is 30000.
transportThe registered transport name. The default is "fetch".
urlOverride the Events route base URL. Normally Citry reads it from the page manifest.

Citry.events.registerTransport

Register a page-wide event transport under a name:

Citry.events.registerTransport("custom", {
  send: async (envelope) => sendThroughHost(envelope),
});

The transport's send method receives one Citry event envelope and returns its result envelope or a Promise for it. Select the transport with Citry.events.configure({ transport: "custom" }).

Citry.events.applyActions

Apply a valid result envelope's actions array to the current page:

await Citry.events.applyActions(result.actions);

The method validates the array, applies its actions in order, and returns a Promise. It is primarily useful for custom transports, integration tests, and hosts that intercept Citry event responses.

The Events guides cover State, template bindings, returned actions, and direct HTTP routes. Alpine runtime covers plugins, CSP, graph markers, and deployment.