Theme
Version
GitHub PyPI Discord
On this page

Vue in Citry

Some interactions should happen in the browser without a call to the server: opening a menu, counting clicks, switching a tab. For these, a Citry component is also a Vue component. Python renders its HTML on the server, and Vue makes it interactive in the browser.

Add a browser counter

Write Vue attributes, such as @click and v-text, in the component's template. In the component's js, call $component({...}) with Vue's component options. $component tells Citry how the component behaves in the browser. Here data() returns the value the browser keeps:

from citry import Component

class Counter(Component):
    template = """
      <button type="button" @click="count += 1">
        Add one
      </button>
      <output v-text="count"></output>
    """

    js = """
      $component({
        data() {
          return { count: 0 };
        },
      });
    """

Each copy of the component on the page keeps its own count.

How Citry runs Vue

You write Vue directives, the Vue attributes such as v-show and @click, in the template, and Vue options in $component({...}). Citry does the rest:

  • Citry compiles each template on the server into the code Vue uses to render it in the browser. There is no build step.
  • Citry loads Vue in the browser, starts it on the HTML the server sent, and applies each new server render to the live page. See Server-rendered HTML.
  • Citry creates the Vue app itself, so there is no Vue app for you to create. Do not mount another Vue app over Citry's output. To add a plugin or a global directive, see Plugins and the Vue app.

When Vue turns on

Citry adds Vue to the page as soon as one component on it does any of these:

  • uses Vue directives in its template;
  • has its own JavaScript;
  • returns data from js_data();
  • uses Vue props or events;
  • keeps server-event State.

Complete pages and HTML fragments work the same way.

Tell Python from Vue

Citry runs {{ ... }} and c-* values as Python on the server, once per render. Vue runs directive values as JavaScript in the browser. Both can sit in one template:

<section c-class="{'has-results': results}">
  <button type="button" @click="open = !open">
    Toggle details
  </button>
  <p v-show="open">{{ details }}</p>
</section>

Python sets the class and inserts details once. After the page loads, Vue changes open. Define open in data(), setup(), or js_data().

A Vue expression cannot see Python names. A common first attempt inside a c-for loop reads the loop name from Vue, and no item gets a title:

<!-- Wrong: Vue looks for `item` in browser data. -->
<li c-for="item in items" :title="item">{{ item }}</li>

<!-- Right: Python sets `title` for each item. -->
<li c-for="item in items" c-title="item">{{ item }}</li>

citry check and the editor report the first form. They also report it when the browser data has its own item: then every item shows that one value, and nothing fails. When Vue needs the value too, for example in a v-show that changes later, send it with js_data() or loop with Vue's v-for.

Choose <c-if> or v-if

Use Vue's v-if and v-for for plain HTML inside one component, and <c-if> and <c-for> when the branch or loop creates Citry components. Choose v-for or <c-for> explains why and shows an example of each.

Where each value goes

Each Python value goes to one place:

PythonReachesRead it as
KwargsThe server only. Nothing is sent to the browser.kwargs.name in Python methods
template_data()The template, on the server{{ name }} or c-* attributes
js_data()The component in the browser, as JSONname in Vue expressions, this.name in JS
css_data()CSS custom propertiesvar(--name) in the component's CSS
StateThe browser, through server events$state.name, this.$state.name

Vue props are separate from all of these: the parent component passes them in the browser with :name.

A Vue expression cannot read a template_data() or Kwargs value. When the browser needs one, return it from js_data() too.

Which Vue version

Citry ships Vue 3.5.42. Each Citry release bundles one fixed Vue version, and you cannot swap in another. To check the version a page runs, enter Citry.vue.version in the browser console.

Where to go next

  • Vue in templates: the v-* directives, @ listeners, and : bindings you write in a template.
  • Component options: data(), methods, js_data(), onServerRender, and the rest of $component({...}).
  • Props and events: pass values to a child component and listen to the events it emits.
  • Content and slots: which component's Vue data the content you pass into a slot reads.
  • Server-rendered HTML: what the page shows before Vue starts, and what it keeps when the server renders again.
  • Plugins and the Vue app: add Vue plugins, global directives, and an error handler.
  • Security and CSP: use a Content Security Policy, or send no JavaScript at all.
  • Limits and errors: the Vue features Citry rejects, and how to find the cause of a failure.
  • Browser APIs: the reference for $component, onServerRender, and Citry.vue.