Theme
Version
GitHub PyPI Discord
On this page

Connect components in the browser

A parent component can pass Python values to a child while it renders. After being rendered and loaded in the browser, components can pass JavaScript values in the client.

You will build a choice button whose label follows its parent. Clicking the child button will change the parent's choice in the browser, and the new label will flow back down to the child.

Start with Add browser behavior if you have not used js_data() with Alpine in Citry yet. This chapter introduces $component because the child needs to declare reactive client props and run setup logic. Components that only expose js_data() values to their own Alpine expressions do not need it.

Build the parent and child

Save this as connected_components.py:

Reactive parent and child components
from citry import Component


class ChoiceButton(Component):
    class Kwargs:
        pass

    class Slots:
        pass

    template = """
      {# 'x-text' shows current value, as given by parent #}
      <button class="choice-button" type="button">
        Choose
        <span
          class="choice-button__label"
          x-text="clientProps.label"
        ></span>
      </button>
    """

    js = """
      $component({
        // Declare the browser value ChoiceButton accepts
        // through '$c-props'.
        props: {
          label: { type: String, required: true },
        },
        init: ({ props, scope }) => {
          // Share the reactive prop with Alpine.
          scope.clientProps = props;
        },
      });
    """


class ChoicePicker(Component):
    class Kwargs:
        pass

    class Slots:
        pass

    template = """
      {# 'choice' lives in the parent's 'x-data'  #}
      <section class="choice-picker" x-data="{ choice: 'Ocean' }">
        <p>
          Current choice:
          <output
            class="choice-picker__value"
            x-text="choice"
          ></output>
        </p>

        {# `$c-props` passes 'choice' down as browser data. #}
        {# `@click` allows parent to react to child's click. #}
        <c-ChoiceButton
          $c-props="{ label: choice }"
          @click="choice = choice === 'Ocean' ? 'Forest' : 'Ocean'"
        />
      </section>
    """


class ChoicePage(Component):
    class Kwargs:
        pass

    class Slots:
        pass

    template = """
      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="utf-8" />
          <title>Connect components</title>
        </head>
        <body>
          <c-ChoicePicker />
        </body>
      </html>
    """


page = ChoicePage()

if __name__ == "__main__":
    print(page)

page

Create the page and open it:

python connected_components.py > connected_components.html

The parent and button both start with β€œOcean.” Click the button and they both change to β€œForest.” Click again and they return to β€œOcean.”

Child client inputs

ChoiceButton.js declares one browser prop called label:

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

Citry checks that the parent supplies a string. The props object stays reactive, so the span can keep reading clientProps.label after the first render.

This is separate from Kwargs. A Kwargs value comes from Python while Citry renders HTML. A client prop comes from browser state and can change without a new Python render.

Pass a reactive value down

The parent supplies the current choice with $c-props:

<c-ChoiceButton $c-props="{ label: choice }" />

The expression runs where the parent wrote it, so choice refers to the parent's Alpine data. Whenever choice changes, Citry updates the child's label prop.

Use $c-props for values that must remain reactive in the browser. Use an ordinary option such as label="Ocean" or a dynamic Python option such as c-label="python_choice" when the value belongs to the server render.

Handle the child's click

The click handler also sits on the child component tag:

<c-ChoiceButton
  $c-props="{ label: choice }"
  @click="choice = choice === 'Ocean' ? 'Forest' : 'Ocean'"
/>

The browser listens on the real button rendered by ChoiceButton, but the expression changes the parent component's choice. This lets a reusable child announce an interaction without needing to know what its parent will do next.

The Client interactivity guide covers multiple roots, slots, handler modifiers, and the complete browser-scope rules.

Next steps

So far every interaction has stayed in the browser. Next, serve the page with FastAPI so a later click can reach a Python handler.