Scripts in components
Not every button needs React. For simple behaviour (opening a menu, copying a link, switching a theme) ordinary JavaScript in a <script> tag inside an .astro component is enough. Astro processes such a script during the build and attaches it to the page.
A "Copy link" button
Create src/components/CopyLink.astro:
<button class="copy-link">Copy link</button>
<script>
const buttons = document.querySelectorAll('.copy-link');
buttons.forEach((button) => {
button.addEventListener('click', async () => {
await navigator.clipboard.writeText(location.href);
button.textContent = 'Copied';
});
});
</script>Put <CopyLink /> into the post page src/pages/posts/[id].astro after <Content />. Open a post and click the button: the page address goes to the clipboard and the label changes to "Copied".
What Astro does with the script
| Feature | What it means |
|---|---|
| Module | The script works as type="module": you can use import |
| TypeScript | You can write TypeScript inside |
| Once | If the component appears three times, the script is included once |
| Bundling | The code is bundled and minified into a separate file |
Because of "once", the script finds all buttons with querySelectorAll rather than one: that way it serves every instance of the component.
Data from the frontmatter
The script runs in the browser and cannot see frontmatter variables. Data is passed through data-* attributes:
---
const { title } = Astro.props;
---
<button class="share" data-title={title}>Share</button>
<script>
document.querySelectorAll<HTMLButtonElement>('.share').forEach((button) => {
button.addEventListener('click', () => {
const title = button.dataset.title;
navigator.share({ title, url: location.href });
});
});
</script>The title prop goes into an HTML attribute, and the script reads it from dataset. In browsers with a share menu, a click opens it with the post title. The <HTMLButtonElement> hint tells TypeScript that buttons were found; otherwise the editor does not know about dataset.
A script without processing
The is:inline attribute leaves a script as is: Astro does not bundle it and inserts it wherever the component appears. This is how third-party snippets, such as a visit counter, are added. A script from the public/ folder loaded with the src attribute is also marked is:inline.
Common mistake
People look up an element with document.querySelector('button') and get the wrong button: the first one on the page rather than the one in their component. Give the component's elements a unique class or a data-* attribute and select by it.