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
Button
Primary / outline button with press animation.
Input
Labeled text field wrapper.
AppBar
Sticky top navigation bar.
SideBar
Slide-in navigation drawer.
BottomNav
Mobile bottom tab bar.
Card
Rounded content surface.
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.