· Experiences · 3 min read

Reactivity in Astro

As I was learning to use the Astro framework, I wanted to learn about idiomatic implementations within Astro. Here is what I learned about implementing reactivity in Astro.

rocketship

While using Astro, I liked how the HTML and CSS can be dynamically generated, however, I hit an issue when building reactive components:

---
function onclick() {
  console.log('clicked')
}
---

<button onclick="{onclick}">Test</button>

Having lived in both svelte and react, you may have expected the above Astro code to be correct and functional. However, the Frontmatter --- fence is purely there for the separation of server-side and client-side code only. Astro does not expose server-side functions to the client, even if they are referenced.

There are a few ways to solve this. On my journey, I had reviewed that Astro has great integrations with a client-side framework, knowing that they did not have strong reactivity support, so I tried adding Vue to my stack.

<script lang="ts" setup>
  function onclick() {
    console.log('click');
  }
</script>

<template>
  <button @click="onclick">Test</button>
</template>

Although this added reactivity, the syntax difference made it awkward to work with both Vue and Astro. Also, adding the client-side framework also increased my build times on every change since we also had to trigger the framework transpilation. But the biggest problem that I ran into, was an issue where Vue components cannot import Astro components, even though Astro components can import Vue components. In my exploration, I learned that this was also true when trying to integrate Svelte.

The solution was to RTFM and actually learn to work within Astro. I read common script patterns and learned that you need to add a client-side script and work within the browser’s JavaScript. I wasn’t ready to jump back into the raw wild west since I was already invested in the Astro framework. Reading on, I found my answer with Astro web components. This is using the web components api to create custom html elements to bind reactivity. You can still bind Astro server-side properties to these web components .

---
const { message = 'clicked' } = Astro.props;
---

<astro-button>
  <button>Test</button>
</astro-button>

<script define:vars="{{ message }}">
  class AstroButton extends HTMLElement {
    // Handle all reactivity in the connected callback
    connectedCallback() {
      const button = this.querySelector('button');
      button.addEventListener('click', () => {
        console.log(message);
      });
    }
  }

  customElements.define('astro-button', AstroButton);
</script>

Final Thoughts

It took some time to understand the point of view of Astro on reactivity. Although Astro web components do have a little more boilerplate than Vue and Svelte, it is compact and simple enough to handle my logic and reactivity.

Back to Blog

Related Posts

View All Posts »
Giving AI Its Own Keys with MCP

Giving AI Its Own Keys with MCP

Connecting AI to email and Notion made it feel like a second brain. Giving it separate accounts and credentials made that useful without handing over the keys to everything else.

Implementing an AI RAG for Bylaw Retrieval

Implementing an AI RAG for Bylaw Retrieval

A bylaw search assistant should find the right clauses without making the rules up as it goes. Building one taught me that retrieval quality, citation quality, and consistency all need their own tests.

Building a Layered Scroll Background with CSS View Timelines

Building a Layered Scroll Background with CSS View Timelines

A layered landscape can make a long page feel alive, but tying several large images to scroll introduces its own design and debugging constraints. I built one with transparent image layers, CSS view timelines, and a few safeguards that keep the content in charge.

© 2026 Sea Otter Studios All rights reserved.