---
title: "Tutorial: Load js-confetti"
description: "Learn how to load the js-confetti script using the Nuxt Scripts module."
canonical_url: "https://scripts.nuxt.com/docs/getting-started/confetti-tutorial"
last_updated: "2026-08-10T04:32:05.889Z"
---

This tutorial loads [js-confetti](https://github.com/loonywizard/js-confetti) from [npm](https://npmjs.com). You'll call it through a proxied function and add types for its browser API.

## Background on [`useScriptNpm()`](/scripts/npm)

[`useScriptNpm()`](/scripts/npm) is a [registry script](/scripts) built on [`useScript()`](/docs/api/use-script). It loads browser-ready package files published to [npm](https://www.npmjs.com/).

Most npm packages belong in `package.json`. Loading one on demand may instead require a dynamic import, a separate chunk, and sometimes build-time transpilation.

`useScriptNpm()` is useful for an occasional, non-critical browser script that already exposes a global API. Install packages your application uses throughout the codebase as normal dependencies.

The three snippets below load the same file at different abstraction levels.

<code-group>

```ts [Registry Script useScriptNpm]
useScriptNpm({
  packageName: 'js-confetti',
  file: 'dist/js-confetti.browser.js',
  version: '0.12.0',
})
```

```ts [useScript]
useScript('https://unpkg.com/js-confetti@0.12.0/dist/js-confetti.browser.js')
```

```ts [useHead]
useHead({
  script: [
    { src: 'https://unpkg.com/js-confetti@0.12.0/dist/js-confetti.browser.js' }
  ]
})
```

</code-group>

### Loading the script

Call [`useScriptNpm()`](/scripts/npm) inside a component:

```vue [app.vue]
<script setup lang="ts">
useScriptNpm({
  packageName: 'js-confetti',
  file: 'dist/js-confetti.browser.js',
  version: '0.12.0',
})
</script>
```

The browser's Network panel should now show the script request.

### Resolving the third-party script API

Tell Nuxt Scripts how to resolve the script's client-side API with the [`use`](/docs/api/use-script#nuxtusescriptoptions) function:

```vue [app.vue]
<script setup lang="ts">
useScriptNpm({
  packageName: 'js-confetti',
  file: 'dist/js-confetti.browser.js',
  version: '0.12.0',
  scriptOptions: {
    // tell useScript how to resolve the third-party script
    use() {
      return { JSConfetti: window.JSConfetti }
    },
  },
})
</script>
```

### Using the third-party script API

The `js-confetti` library exposes a `JSConfetti` class. Create an instance after the script loads, then reuse that instance for subsequent calls.

You can wait for the script explicitly or use a [proxied function](/docs/guides/key-concepts#understanding-proxied-functions) to queue a call until it is ready.

<code-group>

```vue [Explicit Load]
<script setup lang="ts">
const { onLoaded } = useScriptNpm({
  packageName: 'js-confetti',
  file: 'dist/js-confetti.browser.js',
  version: '0.12.0',
  scriptOptions: {
    use() {
      return { JSConfetti: window.JSConfetti }
    },
  },
})
onLoaded(({ JSConfetti }) => {
  // using the real API instance
  const confetti = new JSConfetti()
  confetti.addConfetti({ emojis: ['🌈', '⚡️', '💥', '✨', '💫', '🌸'] })
})
</script>
```

```vue [Proxy Functions]
<script setup lang="ts">
const { proxy } = useScriptNpm({
  packageName: 'js-confetti',
  file: 'dist/js-confetti.browser.js',
  version: '0.12.0',
  scriptOptions: {
    use: () => typeof window.JSConfetti !== 'undefined' && new window.JSConfetti()
  }
})
onMounted(() => {
  // Queued until js-confetti is ready
  proxy.addConfetti({ emojis: ['🌈', '⚡️', '💥', '✨', '💫', '🌸'] })
})
</script>
```

</code-group>

`addConfetti` is still untyped, so the editor cannot check its arguments or offer completion.

### Adding types

Pass a generic to [`useScriptNpm()`](/scripts/npm) and augment `Window` with the same API:

```vue [app.vue]
<script setup lang="ts">
export interface JSConfettiApi {
  JSConfetti: {
    new (config?: { canvas?: HTMLCanvasElement }): {
      addConfetti: (options?: { emojis?: string[] }) => Promise<void>
    }
  }
}

declare global {
  interface Window extends JSConfettiApi {}
}

const { onLoaded } = useScriptNpm<JSConfettiApi>({
  packageName: 'js-confetti',
  file: 'dist/js-confetti.browser.js',
  version: '0.12.0',
  scriptOptions: {
    use() {
      return { JSConfetti: window.JSConfetti }
    },
  },
})
onLoaded(({ JSConfetti }) => {
  const confetti = new JSConfetti()
  // Checked against JSConfettiApi
  confetti.addConfetti({ emojis: ['🌈', '⚡️', '💥', '✨', '💫', '🌸'] })
})
</script>
```

### Delay script loading

Use `trigger` when the script should wait for application state, an event, or a timer.

See the [Script Triggers](/docs/guides/script-triggers) guide for all available options.

#### Using a ref

A `ref` loads the script when its value becomes truthy.

```vue [app.vue]
<script setup lang="ts">
const shouldLoad = ref(false)
const { onLoaded } = useScriptNpm<JSConfettiApi>({
  packageName: 'js-confetti',
  file: 'dist/js-confetti.browser.js',
  version: '0.12.0',
  scriptOptions: {
    trigger: shouldLoad,
    use: () => ({ JSConfetti: window.JSConfetti }),
  },
})
onLoaded(({ JSConfetti }) => {
  const confetti = new JSConfetti()
  confetti.addConfetti({ emojis: ['🎉', '🎊', '✨'] })
})
</script>

<template>
  <button @click="shouldLoad = true">
    Click to load confetti
  </button>
</template>
```

<tip>

You can also use a computed ref or getter function: `trigger: computed(() => someCondition.value)` or `trigger: () => shouldLoad.value`.

</tip>

#### Using element events

Use [`useScriptTriggerElement()`](/docs/api/use-script-trigger-element) to wait for an element interaction.

```vue [app.vue]
<script setup lang="ts">
const mouseOverEl = ref<HTMLElement | null>(null)
const { onLoaded } = useScriptNpm<JSConfettiApi>({
  packageName: 'js-confetti',
  file: 'dist/js-confetti.browser.js',
  version: '0.12.0',
  scriptOptions: {
    trigger: useScriptTriggerElement({ trigger: 'mouseover', el: mouseOverEl }),
    use: () => ({ JSConfetti: window.JSConfetti }),
  },
})
onLoaded(({ JSConfetti }) => {
  const confetti = new JSConfetti()
  confetti.addConfetti({ emojis: ['L', 'O', 'A', 'D', 'E', 'D'] })
})
</script>

<template>
  <div ref="mouseOverEl">
    <h1>Hover over me to load the confetti</h1>
  </div>
</template>
```

### Bundle the script locally

Nuxt Scripts bundles statically analyzable `useScriptNpm()` files by default and serves them from `/_scripts/assets/`. This avoids the initial connection to the package CDN.

Set `bundle: false` if you want to load the file directly from the configured CDN instead.

```vue [app.vue]
<script setup lang="ts">
useScriptNpm({
  packageName: 'js-confetti',
  file: 'dist/js-confetti.browser.js',
  version: '0.12.0',
  scriptOptions: {
    bundle: false,
  },
})
</script>
```

Without this opt-out, the Network panel shows the script loading from your application's server. See [Key Concepts](/docs/guides/key-concepts) for more on script instances and proxied functions.
