Say goodbye to nesting hell, embrace fluent development experience
VanityH is not just another complex UI framework. It's a minimal DSL (Domain-Specific Language) builder. Using Proxy and closure logic, it transforms verbose h(tag, props, children) calls into a fluent, chainable syntax similar to SwiftUI or Flutter.
In non-JSX environments (vanilla JS/TS, scripting tools, low-code engines), developers face these challenges:
- Nesting Hell: Traditional
hfunctions require heavy object nesting, creating visual noise - Prop Mutation: Component reuse often accidentally pollutes original definitions
- Cognitive Load: Properties, events, and child nodes are interleaved, making DOM structure hard to understand
- Environment Dependencies: JSX requires compilation setup, not suitable for lightweight use in native browser environments
VanityH perfectly resolves these issues with "chainable configuration + terminator rendering" logic.
VanityH separates property configuration from node mounting syntax, creating perfect mapping between code structure and DOM structure.
html.lang('en')(
head(
meta.charset('UTF-8')(),
link.rel('icon').type('image/svg+xml').href('/favicon.svg')(),
title('VanityH – Elegance Redefined'),
),
body(div.id('app')(), script.type('module').src('/src/main.ts')()),
)Based on Copy-on-Write philosophy, each property call produces a brand-new state snapshot.
const baseBtn = button.class('btn')
const redBtn = baseBtn.style('color: red')('Red Button')
const blueBtn = baseBtn.style('color: blue')('Blue Button') // baseBtn remains pureTools should not be smarter than developers. VanityH doesn't auto-handle booleans, no implicit conversions, fully transparent.
- Size: Just 186 bytes, ultra-minimal implementation
- Compatibility: Supports Vue, Preact, React, Snabbdom, and any hyperscript-compatible renderer
NPM:
npm install vanity-hCDN (No Build Step Required):
<script type="module">
import { render, h } from 'https://esm.sh/preact'
import createVanity from 'https://esm.sh/vanity-h'
const { div, span } = createVanity(h)
const app = () => div.class('app')(span('Hello World'))
render(app(), document.getElementById('app'))
</script>// Traditional hyperscript
h('div', { class: 'card', style: 'padding: 20px' }, [
h('button', { class: 'btn-primary', onClick: handleClick }, 'Click me'),
])
// VanityH syntax
div.class('card').style('padding: 20px')(
button.class('btn-primary').onClick(handleClick)('Click me'),
)VanityH ships with first-class adapters for Vue, React, and Preact with full TypeScript support.
import vanity, { defineComponent } from 'vanity-h/vue'
import { createApp } from 'vue'
const { div } = vanity
// Option A: array emits — event handlers are typed but parameters are `any`
const MyComp = defineComponent(
(props: { name: string; age: number }) => {
return () => div.class('demo')(props.name, props.age)
},
{ props: ['name', 'age'], emits: ['say'] },
)
MyComp.$.name('Tom')
.age(20)
.onSay(() => {})() // ✅ onSay exists and is checked
// Option B: object emits — full parameter type inference
const MyComp2 = defineComponent(
(props: { name: string }) => {
return () => div(props.name)
},
{
props: ['name'],
emits: { say: (word: string) => !!word },
},
)
MyComp2.$.name('Tom').onSay((word) => console.log(word))() // ✅ word: string
MyComp2.$.name(123)() // ❌ type error
createApp(defineComponent(() => () => div())).mount('#app')Emits type inference levels:
emits style |
onXxx handler type |
|---|---|
Array ['say'] |
((...args: any[]) => any) | undefined |
Object { say: (word: string) => ... } |
((word: string) => any) | undefined |
import vanity, { defineComponent } from 'vanity-h/react'
const { div } = vanity
const MyComp = defineComponent(({ name, age }: { name: string; age: number }) => {
return div(name, age)
})
// $ provides typed prop chaining
MyComp.$.name('Tom').age(20)() // ✅import vanity, { defineComponent } from 'vanity-h/preact'
const { div } = vanity
const MyComp = defineComponent(({ name }: { name: string }) => {
return div(name)
})
MyComp.$.name('Tom')() // ✅The $ property is a shorthand equivalent to x() — it wraps any component for typed prop chaining:
// These are equivalent
x(MyComp).name('Tom').age(20)()
MyComp.$.name('Tom').age(20)()$ is implemented as a global Object.prototype getter at runtime. Framework adapters' defineComponent is purely a type-level wrapper — it returns the component as-is with no runtime overhead. The global getter handles everything at runtime.
When using framework adapters with defineComponent, $ carries full prop type inference. On components not wrapped with defineComponent (e.g. Vue built-ins like Transition), $ is untyped but still callable:
import { Transition } from 'vue'
Transition.$.name('fade')() // works, untypedVanityH internally uses JavaScript's Proxy to intercept get operations, combined with recursive closures to manage state:
- Configuration Mode: Accessing properties returns a new Proxy with internal closure holding accumulated
propsobject - Execution Mode: When Proxy is called as function, it submits
propsandchildrento the renderer
import createVanity, { type VanityH } from 'vanity-h'
import { h, type VNode } from 'vue'
const v: VanityH<VNode> = createVanity(h)
const element = v.div.class('test').id('app')('Content')Framework adapters provide deeper type inference — see Framework Adapters above.
- Size: 186 bytes (minified) / ~150 bytes (gzipped)
- Zero Dependencies: Pure JavaScript implementation
- High Performance: Proxy interception overhead is negligible
- Memory Friendly: Closure-based immutable design
git clone https://github.com/VanityH/vanityh.git
cd vanityh
vp install # install dependencies
vp check # type check + lint
vp test # run tests
vp pack # build libraryMIT License © 2026 VanityH Team
VanityH: Make writing render functions a pleasure, not a pain.
- HTM - JSX-like syntax in plain JavaScript
- DLight - DX-first UI rendering library
- Hyperscript - Create HTML with JavaScript
- SwiftUI - Declarative UI framework