React, Next.js & other frameworks
Drop a Vuesax element into Next.js, React, Nuxt, Vue, Svelte, Angular or Astro — typed, server-rendered, no glue code.
TL;DR · one file, every framework
- Every
vs-*.jsfile is safe to import on the server. Next.js, Nuxt, Astro and SvelteKit render the page without a single guard. - React and Next.js get a generated wrapper (
VsX.tsx) and TypeScript types (vs-x.d.ts) next to the code. - Everything else needs one line of setup at most.
Every item is a native custom element. The element itself is the same in every framework; what changes is how your framework passes arrays, listens to events and type-checks the tag. This page covers each one.
What you get
The code panel of every entry (and the MCP server) hands you:
| File | What it is | Who needs it |
|---|---|---|
vs-x.js | The component. Zero dependencies, occasionally with a sibling vs-*.js it reuses. | Everyone |
vs-x.d.ts | TypeScript types: the element, its attributes, its events, and the HTMLElementTagNameMap entry. | TypeScript projects |
VsX.tsx | A React wrapper: typed props, on… event props, arrays and objects passed as properties. | React and Next.js |
Keep them together in one folder — the wrapper imports the .js and the .d.ts
by relative path.
Next.js
Works in the App Router and the Pages Router, on React 18 and 19.
- Put the files in a folder, for example
components/vuesax/:
components/vuesax/
vs-nav-menu.js
vs-fx.js ← sibling it imports (when there is one)
vs-nav-menu.d.ts
VsNavMenu.tsx
- Render the wrapper — from a Server Component, a Client Component or a page:
// app/page.tsx — a Server Component is fine
import { VsNavMenu } from '@/components/vuesax/VsNavMenu';
const items = [
{ value: 'docs', label: 'Docs', layout: 'list', links: [{ title: 'Install', desc: 'Guides' }] },
{ value: 'blog', label: 'Blog', href: '/blog' },
];
export default function Page() {
return <VsNavMenu variant="ghost" items={items} />;
}
That’s it. No dynamic(() => import(…), { ssr: false }), no useEffect import,
no custom declare global block.
- The wrapper is a client component (it starts with
'use client'), so event props work:onSelect={(e) => router.push(e.detail.item.href)}inside any client component. - The server renders
<vs-nav-menu variant="ghost">with its attributes; the browser upgrades it when the script loads. refgives you the element itself, typed asVsNavMenuElement.
"[object Object]" and never listens to custom events. React 19
fixes part of that, but not booleans that default to on. The wrapper writes
every prop to the element with its real type and binds on… props to
the element's events, so the same code behaves the same on both.
React (Vite, Remix, CRA)
Exactly the same files and the same usage as Next.js — the 'use client' line
is ignored outside Next.
import { VsButton } from './vuesax/VsButton';
<VsButton label="Save" variant="primary" onClick={save} />
If you prefer the raw tag, import the file once and use <vs-button> directly;
set arrays through a ref (ref.current.items = items) and listen with
addEventListener.
Nuxt and Vue
Tell the template compiler that vs-* tags are custom elements, import the file
once, and use the tag:
// nuxt.config.ts
export default defineNuxtConfig({
vue: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('vs-') } },
});
// Vue + Vite: vite.config.ts
vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('vs-') } } });
<script setup>
import './vuesax/vs-nav-menu.js'; // SSR-safe, no <ClientOnly> needed
</script>
<template>
<vs-nav-menu variant="ghost" :items.prop="items" @select="onSelect" />
</template>
.prop sends arrays and objects as properties; @event listens to the
element’s events.
Svelte and SvelteKit
<script>
import './vuesax/vs-nav-menu.js';
</script>
<vs-nav-menu variant="ghost" items={items} onselect={onSelect}></vs-nav-menu>
Svelte 5 sets arrays and objects as properties automatically. On Svelte 4 use
on:select.
Angular
@Component({
selector: 'app-nav',
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `<vs-nav-menu variant="ghost" [items]="items" (select)="onSelect($event)"></vs-nav-menu>`,
})
export class NavComponent {}
Import the file once, in main.ts or the component.
Astro
<vs-nav-menu variant="ghost"></vs-nav-menu>
<script>
import '../vuesax/vs-nav-menu.js';
document.querySelector('vs-nav-menu').items = [/* … */];
</script>
No client:only needed.
Plain HTML
<script type="module" src="./vs-nav-menu.js"></script>
<vs-nav-menu variant="ghost"></vs-nav-menu>
Server-side rendering, in detail
A server has no DOM, so a component file that touched HTMLElement or
customElements the moment it was imported would crash the page before it
rendered. Every Vuesax file is written so that importing it on a server does
nothing at all:
- the element class falls back to an empty base class when
HTMLElementis missing; customElements.defineonly runs when a registry exists — and never twice, so hot reload and duplicate bundles don’t throw;- window listeners and other browser-only setup wait for a browser.
Each file is checked by importing it in bare Node before it ships. The page renders the tag with its attributes; the element comes alive when the browser runs the script.
A few animations draw with WebGL. They render nothing on the server and start the moment the browser upgrades them — there’s nothing to wrap.
three
Almost every file is zero-dependency. The three that build on
three.js say so at the top of the file and
import it by name — run npm install three once if you add one of them.