Referencing 'This' in Astro

May 22, 2025 ¡ Tyler Yeager ¡ 4 min reading time

This Site

Astro is a web development framework. Where it really shines is its static site generation and usage of islands, which allows you to segment your website.

As of the date of this post, this site uses Astro.

If you are using Firefox or Chrome, you can install an addon called “React Developer Tools” that lets you discover whether the page you have loaded is using React or not.

If you navigate to the home page, you’ll notice that react is not detected with this message from the addon:

This page doesn’t appear to be using React.

That’s because it’s not. React is not used on the home page. However, React is used on other areas, such as some interactive posts. On the front page, only vanilla javascript is used. At the time of this writing, it handles jumping to certain sections of the page, as well as the bicycle weather app and the toggling of the dark/light theme.

Reusing Components

When building these, I have been running into a stumbling block. Many of my components use javascript and are used multiple times. How do I prevent them from colliding?

Take this example, you’ll notice that with c we get the element by its id and then at b we blur the component when we click on it.

BlurText.astro
---
---
<div id="my-component">Some Text</div>
<script>
const component = document.getElementById("my-component");
if(component) {
component.addEventListener('click', () => {
component.classList.toggle("blur")
component.classList.toggle("blur-sm")
})
}
</script>

This works great, right until we reuse the same component again on the same page somewhere. Now there’s multiple elements with the same id. One of them will still work, but the others won’t!

How can we reliably reference the right element?

Random UUID

The solution I came up is to generate a UUID u for each component. Each time this component is called during rendering, a new uuid is generated and used for that component. Solved!

However, you might notice ut that this script now requires is:inline and define:vars={{uuid}}. Regarding that, these reference Script & Style Directives Astro offers.

I recommend reading this page, but what it boils down to is Astro will try to compile your scripts. Since each time we call our component, the code has to be a little different, it will just write it inline the page for each time we call it. This allows define:vars to work.

BlurText.astro
---
const uuid = crypto.randomUUID()
---
<div id={uuid}>Some Text</div>
<script is:inline define:vars={{uuid}}>
const component = document.getElementById(uuid);
if(component) {
component.addEventListener('click', () => {
component.classList.toggle("blur")
component.classList.toggle("blur-sm")
})
}
</script>

Create Your Own Element

That all being said, Astro does offer its own solution that gets around this problem. Essentially, you d set a data attribute in the html. They show a way to reference it r by using an extended element.

AstroGreet.astro
---
const { message = 'Welcome, world!' } = Astro.props;
---
<!-- Store the message prop as a data attribute. -->
<astro-greet data-message={message}>
<button>Say hi!</button>
</astro-greet>
<script>
class AstroGreet extends HTMLElement {
connectedCallback() {
// Read the message from the data attribute.
const message = this.dataset.message;
const button = this.querySelector('button');
button.addEventListener('click', () => {
alert(message);
});
}
}
customElements.define('astro-greet', AstroGreet);
</script>

Taking this strategy we’ll convert our original example to this style to get past the limitations with is:inline. First we u define the custom element, astro-blur. Then on our callback, we now have access to a this e , allowing the code to reference itself and add the click functionality.

BlurText.astro
---
---
<astro-blur>SomeText</astro-blur>
<script>
class AstroBlur extends HTMLElement {
connectedCallback() {
this.addEventListener('click', () => {
this.classList.toggle("blur")
this.classList.toggle("blur-sm")
})
}
}
</script>
customElements.define('astro-blur', AstroBlur)

Real World Examples

With an HTML Element

In my review section, I didn’t want to give spoilers while also allowing those interested to be able to reveal them anyway. So I wrote up this Astro component. Note that it is a little more advanced, using a typed interface t for the props. There’s some other r functionality to help with spacing for the concealed text as well.

What we’re going to do is remove u the uuid references, create a new element again (AstroBlur) and use that instead.

BlurText.astro
---
const uuid = crypto.randomUUID()
interface Props {
text: string,
}
const { text } = Astro.props;
const randomizedText = text.split('')
.sort(() => 0.5-Math.random())
.join('')
.toLowerCase()
---
<span id={uuid} class="blurred-element bg-base-content opacity-20 cursor-help" data-content={text}>{randomizedText}</span>
<script is:inline define:vars={{uuid}}>
const b = document.getElementById(uuid)
if(b) {
b.addEventListener("click", () => {
b.innerText = b.getAttribute("data-content") || "";
b.classList.remove("opacity-20");
b.classList.remove("cursor-help");
b.classList.remove("bg-base-content");
})
}
</script>

Alright, as you can see c , we have a new component called astro-blur. Following the previous strategy, we use the connectedCallback to do essentially the exact same thing, using this instead of referencing it with b.

One note: don’t forget to define the element d with customElements, or it won’t know what it is! Had to debug that one 😅

BlurText.astro
---
interface Props {
text: string,
}
const { text } = Astro.props;
const randomizedText = text.split('')
.sort(() => 0.5-Math.random())
.join('')
.toLowerCase()
---
<astro-blur class="blurred-element bg-base-content opacity-20 cursor-help" data-content={text}>{randomizedText}</astro-blur>
<script>
class AstroBlur extends HTMLElement {
connectedCallback() {
this.addEventListener("click", () => {
this.innerText = this.getAttribute("data-content") || "";
this.classList.remove("opacity-20");
this.classList.remove("cursor-help");
this.classList.remove("bg-base-content");
}
}
customElements.define('astro-blur', AstroBlur)
</script>

Did it work? Feel free to check out my reviews, since I updated the code to use this now 😊

With an Astro Element

However, this approach doesn’t always work. Consider the Image component with Astro.

StyledImage.astro
8 collapsed lines
---
const {
src,
width,
height,
} = Astro.props
---
<Image
src={src}
width={width}
height={height}
/>

The <Image /> will later be converted to an <img />, so we can’t extend an HTMLElement, because it’s not an actual element. How do we address that? Well, we can use the original method by providing i an id. Now by using the same method as earlier, we can reference it r and do whatever actions we want.

StyledImage.astro
---
6 collapsed lines
const {
src,
width,
height,
} = Astro.props
const uuid = crypto.randomUUID()
---
<Image
id={uuid}
src={src}
width={width}
height={height}
/>
<script is:inline define:vars={{uuid}}>
const component = document.getElementById(uuid);
6 collapsed lines
if(component) {
component.addEventListener('click', () => {
component.classList.toggle("blur")
component.classList.toggle("blur-sm")
})
}
</script>

Conclusion

The uuid approach was the goto I’ve been using, but as long as you have access to the direct HTML element, extending it can also work. Make sure to keep in mind that your CSS may need to change if it depends on that.

This is also relevant for Astro specific files. You can alternatively use other UI Frameworks if the extra cost of loading in a larger library is ok. The perk of this vanilla javascript approach is it is well-supported, fast, and works well on low power devices. SEO also benefits since you’re not requiring Javascript to see the original text.

On the other hand, if you’re requiring a lot of interactivity and changes, you will save a lot of stress by using React and friends.

If you use the React addon mentioned at the start, you’ll see that this site makes use of Reacts in various posts and sections. I figure if you’re browsing around, you’re ok with the interactive ones taking a moment longer to load. If you’re not, it only needs javascript for the interactive parts. Islands are great ❤️

Did you find this interesting?

Consider subscribing 😊

No AI-generated content or SEO garbage.

Unsubscribe anytime