Docs / Components
ven.js

Components

VenJS ships a small set of prebuilt, composable UI components. In real projects each page gets its own file under components/ (e.g. home.js, about.js, login.js) and imports the shared UI kit as needed.

All components are functions on venjs that return a VNode. You can pass children and props exactly like venjs.div(). They live in the engine's UI object in ven.js:263.

Component catalog

Page component pattern

Each page file exports a single component function and attaches it to window so the router can reach it. The logic/ folder handles API calls and state workflows.

// components/home.js
const HomePage = () => venjs.div({ class: "page" }, [
  venjs.h1({ class: "page-title" }, "Welcome"),
  venjs.p({ class: "page-copy" }, "This is the home page.")
]);
window.HomePage = HomePage;

// components/login.js
import { loginUser } from "../logic/login.js";

const LoginPage = () => {
  const email = venjs.signal("");
  const password = venjs.signal("");
  const status = venjs.signal("");

  const submit = async () => {
    try {
      const user = await loginUser(email.value, password.value);
      status.value = "Welcome, " + user.email;
    } catch (e) {
      status.value = "Login failed: " + e.message;
    }
  };

  return venjs.div({ class: "page" }, [
    venjs.h1({}, "Sign in"),
    venjs.input({ label: "Email", value: email.value, oninput: (e) => (email.value = e.target.value) }),
    venjs.input({ label: "Password", type: "password", value: password.value, oninput: (e) => (password.value = e.target.value) }),
    venjs.button({ onclick: submit }, "Sign in"),
    status.value ? venjs.p({}, status.value) : null
  ]);
};
window.LoginPage = LoginPage;

Quick example

venjs.render(app, () => venjs.div({}, [
  venjs.appBar({ title: "Acme", links: [ venjs.a({ href: "#" }, "Home") ] }),
  venjs.card({}, [
    venjs.h2({}, "Sign in"),
    venjs.input({ label: "Email", type: "email", placeholder: "you@example.com" }),
    venjs.button({ variant: "outline", onclick: submit }, "Continue")
  ])
]));
Styling dependency: Components apply utility classes such as px-6 py-2.5 rounded-xl. They render correctly without Tailwind, but the spacing/radius polish expects a utility-CSS framework. You can override class via the class prop, which is appended after the defaults.

Quick example

venjs.render(app, () => venjs.div({}, [
  venjs.appBar({ title: "Acme", links: [ venjs.a({ href: "#" }, "Home") ] }),
  venjs.card({}, [
    venjs.h2({}, "Sign in"),
    venjs.input({ label: "Email", type: "email", placeholder: "you@example.com" }),
    venjs.button({ variant: "outline", onclick: submit }, "Continue")
  ])
]));
Styling dependency: Components apply utility classes such as px-6 py-2.5 rounded-xl. They render correctly without Tailwind, but the spacing/radius polish expects a utility-CSS framework. You can override class via the class prop, which is appended after the defaults.