On this page
Web Components and Native APIs
When you need interactivity, use custom elements (web components) with native JavaScript. Do not use React, Vue, Svelte, jQuery, or other frameworks.
Why Custom Elements
- No framework runtime to ship
- Works natively with Astro’s HTML-first approach
- State is managed in the component class
- No hydration complexity
See the Astro documentation on custom elements for more details.
Counter Example
✅ Good (Custom Element):
<counter-button>
<button>Count: <span>0</span></button>
</counter-button>
<script>
class CounterButton extends HTMLElement {
private count = 0
connectedCallback() {
const span = this.querySelector('span')
this.querySelector('button')?.addEventListener('click', () => {
this.count++
if (span) span.textContent = String(this.count)
})
}
}
customElements.define('counter-button', CounterButton)
</script>
❌ Avoid (React in Astro):
---
import Counter from '../components/Counter.tsx'
---
<!-- Requires @astrojs/react integration, ships React runtime -->
<Counter client:load />
// Counter.tsx - separate file needed
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
)
}
❌ Avoid (Pure JavaScript with global state):
<button id="counter-btn">Count: 0</button>
<script>
let count = 0
const btn = document.getElementById('counter-btn')
btn?.addEventListener('click', () => {
count++
btn.textContent = `Count: ${count}`
})
</script>
❌ Avoid (jQuery-style with querySelector):
<button class="js-counter">Count: 0</button>
<script>
let count = 0
document.querySelector('.js-counter')?.addEventListener('click', (e) => {
count++
;(e.target as HTMLElement).textContent = `Count: ${count}`
})
</script>
Key Principles
- Custom element tag: Use a hyphenated name (e.g.,
counter-button,copy-link) - State in class: Store state as class properties
- connectedCallback: Initialize when element is added to DOM
- No global selectors: Query within
this, notdocument