Component renders but buttons/events don't work
Your Astro component renders as static HTML. You need a hydration directive to make it interactive.
Why this happens
Astro renders all components as static HTML by default β this is called βIslands Architecture.β Unlike frameworks like Next.js or Nuxt, Astro ships zero JavaScript unless you explicitly opt in. If you forget to add a client:* directive, the componentβs HTML appears on the page but none of its event handlers, state, or interactivity will work.
Fix 1: Add a client directive
---
import Counter from './Counter.jsx';
---
<!-- β Static β no interactivity -->
<Counter />
<!-- β
Interactive on page load -->
<Counter client:load />
<!-- β
Interactive when visible -->
<Counter client:visible />
<!-- β
Interactive on idle -->
<Counter client:idle />
Fix 2: Choose the right directive
client:loadβ hydrate immediately (above the fold)client:idleβ hydrate when browser is idle (below the fold)client:visibleβ hydrate when scrolled into viewclient:mediaβ hydrate at a media query breakpointclient:onlyβ skip SSR, client-render only
Alternative solutions
If you only need a small amount of interactivity (like a toggle or accordion), consider using vanilla JavaScript in a <script> tag instead of hydrating an entire framework component:
<button id="toggle">Click me</button>
<script>
document.getElementById('toggle')?.addEventListener('click', () => {
alert('No framework needed!');
});
</script>
You can also use client:only="react" if the component relies on browser-only APIs and doesnβt need SSR at all.
Prevention
- Make it a habit to always add a
client:*directive when importing interactive framework components (React, Vue, Svelte, etc.) into.astrofiles. - Use
client:visibleorclient:idleby default to keep your page fast, and only reach forclient:loadwhen the component is above the fold and needs to be interactive immediately.
Related: Astro: Content Collection Schema Validation Error Β· Vite vs Webpack