Skip to content
thednp edited this page Jun 8, 2025 · 10 revisions

JSX transformation is a nice way to streamline your workflow and has its own advantages, specifically for VanJS here you don't have to define the tags your app needs for every page and every component. The feature is enabled by default, with minimal to no compilation at runtime, so you don't need to do anything except to add Typescript support if you need to. Edit your tsconfig.json as follows:

{
  "compilerOptions": {
    /** other compilerOptions */
    "jsx": "preserve",
    "jsxImportSource": "@vanjs/jsx"
  }
}

Note the debug mode in VanJS is not compatible with JSX transformation.

Basic Example

// App.tsx
import van from 'vanjs-core';

const App = () => {
  const count = van.state(0);
  const btnRef = van.state<{ current: HTMLElement }>();
  return (
    <button ref={btnRef} onClick={() => count.val++}>{count}</button>
  );
}

const root = document.getElementById("app") as HTMLElement;

// we enforce HTMLElement because van.add only recognizes this type
van.add(root, <App /> as HTMLElement);

Fragment Example

// App.tsx
import van from 'vanjs-core';

const App = () => {
  const count = van.state(0);
  return (
    <>
      <h3>Count Action</h3>
      <button onClick={() => count.val++}>Count: {count}</button>
    </>
  );
}

const root = document.getElementById("app") as HTMLElement;

// we enforce HTMLElement because van.add only recognizes this type
van.add(root, <App /> as HTMLElement);

Style Examples

// Style Examples
const StyleExample = () => {
  // Object style
  const objStyle = {
    color: "blue",
    fontSize: "16px"
  };

  // String style
  const strStyle = "color: red; font-size: 16px;";

  return <>
    <div style={objStyle}>Object Style</div>
    <div style={strStyle}>String Style</div>
  </>;
}

Typed Component Example

interface Props {
  title: string;
  count?: number;
}

const TypedComponent = ({ title, count = 0 }: Props) => {
  return <div>
    <h1>{title}</h1>
    <p>Count: {count}</p>
  </div>
};

// Usage
const App = () => {
  return <TypedComponent title="Hello" count={5} />;
};

SVG example

import van from "vanjs-core";

const App = () => {
  return (
    <>
      <h1>SVG Example</h1>
      <svg width={100} height={100} xmlns="http://www.w3.org/2000/svg">
        <circle cx={50} cy={50} r={40} fill="blue" />
      </svg>
    </>
  );
};

MathML example

import van from "vanjs-core";

const App = () => {
  return (
    <>
      <h1>Quadratic Formula Example</h1>
      <math display="block">
        <mrow>
          <mi>x</mi>
          <mo>=</mo>
          <mfrac>
            <mrow>
              <mo></mo>
              <mi>b</mi>
              <mo>±</mo>
              <msqrt>
                <msup><mi>b</mi><mn>2</mn></msup>
                <mo></mo>
                <mn>4</mn>
                <mi>a</mi>
                <mi>c</mi>
              </msqrt>
            </mrow>
            <mrow>
              <mn>2</mn>
              <mi>a</mi>
            </mrow>
          </mfrac>
        </mrow>
      </math>
    </>
  );
};

Form example

// Form.tsx
import van from "vanjs-core";

const Form = () => {
  const username = van.state("");
  const email = van.state("");

  const handleSubmit = (e: Event) => {
    e.preventDefault();
    console.log("Form submitted:", {
      username: username.val,
      email: email.val,
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label for="username">Username:</label>
        <input
          id="username"
          type="text"
          value={username.val}
          onInput={e => username.val = e.target.value}
        />
      </div>

      <div>
        <label for="email">Email:</label>
        <input
          id="email"
          type="email"
          value={email.val}
          onInput={e => email.val = e.target.value}
        />
      </div>

      <button type="submit">Submit</button>
    </form>
  );
};

const root = document.getElementById("app") as HTMLElement;
van.add(root, <Form /> as HTMLElement);

VanJS JSX Components

In some cases you may want to create components with parent-child relationship that work in both classic VanJS components and JSX components. A good example is the anchor component.

Let's showcase this specific implementation (pay attention to the comments):

// src/components/MyComponent.ts
import van from "vanjs-core";
import { type ChildDom } from "vanjs-core";

// define types for your component
type MyComponentProps = {
  children: ChildDom[]
  // ...others
}

export const MyComponent = (
  { children, ...rest }: MyComponentProps = {}, // children here is for JSX usage
  ...otherChildren: ...ChildDom[]               // otherChildren here is for classic VanJS
) => {

  // for JSX it's important to make sure that values are not undefined
  const props = Object.fromEntries(
    Object.entries(rest || {}).filter(([_, val]) => val !== undefined),
  );

  // return any van.tags instance, VanJS will spread the children itself
  return van.tags.div(props, (children || otherChildren));
};

Usage with JSX:

// src/pages/Home.tsx
import MyComponent from "../components/MyComponent";

export const Page () => {
  return (
    <>
      <h1>Hello VanJS!</h1>
      <MyComponent>
        This is a child node.
      </MyComponent>
    </>
  );
}

Usage without JSX:

// src/pages/Home.ts
import van from "vanjs-core";
import MyComponent from "../components/MyComponent";

export const Page () => {
  const { h1 } = van.tags;

  return [
    h1("Hello VanJS!"),
    MyComponent({},
      "This is a child node."
    )
  ];
}

Notes

  • in some cases, enforcing a certain typescript type via as might be in good order depending on who renders your component;
  • similar to other frameworks you can use ref as an instance of van.state, you can do whatever you need with ref.val.current;
  • similar to SolidJS and different from React, you can use class attribute instead of className, for attribute instead of htmlFor;
  • you can use style as both an object and a string, which is handled by the plugin at runtime;
  • for a series of JSX starter templates, check out the create-vanjs.
Clone this wiki locally