#Call vanilla js function from Astro template with simplicity of framework integration

4 messages · Page 1 of 1 (latest)

dense epoch
#

I want to call a js function, client side, within an Astro template.

I could use this with 2 script tags, the first to attach the variables to the window:

---
const { myVar } = Astro.locals;
---
<script is:inline define:vars={{ myVar }}>
  window.myVar = myVar
</script>

and the second to call my function:

<script>
import { someFunction } from "@/utils"
someFunction(window.myVar)
</script>

Alternatively I could use React:

---
const { myVar } = Astro.locals;
---
<RunMyFunction client:only var={myVar} />
function RunMyFunction({myVar}) {
  someFunction(myVar);
  return null;
}

But this seems messy and introduces a dependency on React.

I read in the docs about attaching variables to html elements, using data attributes, and then accessing them in scripts - but I don't need an html element other than the script tag, and adding a data attribute to the script tag will make it infer is:inline, so I can't then import my function.

Is there way to call Vanilla JS functions, passing variables to them, with the neat API that frameworks like React have?

tall cliffBOT
#

Hi @dense epoch. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

You can get very close to the “nice API” of framework components by combining:

  • normal Astro frontmatter variables
  • a processed <script> (so you can import and bundle)
  • either data-* attributes or define:vars (with trade‑offs)

From the docs, there are two recommended patterns.


1. Use a data attribute on a real element (recommended)

Astro frontmatter runs on the server; scripts run in the browser. To pass values, serialize them into the HTML, typically via data-* attributes, and read them in your script. [Pass variables]

---
import { someFunction } from '@/utils';
const { myVar } = Astro.locals;
---

<div data-my-var={JSON.stringify(myVar)}></div>

<script>
  import { someFunction } from '@/utils';

  const el = document.querySelector('[data-my-var]');
  const myVar = el && JSON.parse(el.dataset.myVar ?? 'null');

  if (myVar != null) {
    someFunction(myVar);
  }
</script>

Here:

  • The <script> has no attributes → it is processed, TypeScript-enabled, and can import from local or npm modules. [Script processing]
  • The extra <div> is the only “messy” part, but this is exactly the pattern Astro documents and is what it does internally for framework islands. [Pass variables]

2. Use define:vars on <script> (inline, no imports)

define:vars lets you inject frontmatter variables directly into a <script>, but it implies is:inline, so that script will not be bundled and cannot use import. [define:vars]

#
---
const { myVar } = Astro.locals;
---

<script define:vars={{ myVar }}>
  // myVar is available directly here
  // But: no imports / bundling, this is raw inline JS
  window.myVar = myVar;
</script>

If you only need small, self‑contained JS, this is fine; if you need imports, stick with pattern 1.


Why you can’t do “React‑style” directly

  • Astro components themselves cannot be hydrated with client:*; they are server‑only templates. [Hydrate Astro?]
  • Adding any attribute (including data-*) to <script> implies is:inline, which turns off bundling and import resolution. [is:inline; Unprocessed scripts]

So the closest, idiomatic Astro approach to “<RunMyFunction var={myVar} /> in React” is:

  1. Serialize myVar into HTML (usually a data-* attribute on some element).
  2. Use a processed <script> (no attributes) to read it and call your vanilla function with full bundling and imports.

There’s no built‑in, framework‑like client directive for plain scripts beyond this documented pattern.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: