An extremely lightweight, zero-config, zero-dependency, VDOM-less embedded front-end framework. No build step required. Write plain H5 DOM, drive the view with template directives, and do all styling inline with an atomic CSS engine.
- About
- Features
- Quick Start
- Installation
- Demos
- Scope
- Template Directives
- Data Binding
- class / style Shorthands
- Atomic CSS
- Events
- Dynamic Props / Events & Custom Directives
- Variables
- Functions & API
- Components (z-comp)
- z-json Utilities
- z-data studio (z-pen)
- Browser Compatibility
- Project Structure
- Performance
- Join Us
- License
z-data is a minimal reactive front-end framework built around a simple idea:
- No virtual DOM — operates directly on the H5 DOM for high performance.
- Zero config, zero dependency, no build step — a single JS file, plug and play.
- Embeddable — coexists seamlessly with other frameworks (e.g. Vue, Alpine).
- Template directives — supports
for / if / else / use. - Atomic CSS — a Tailwind-like rules engine that generates styles inline, with no build tools.
- Rich syntax sugar — especially friendly for
classandstyle.
z-data drives view and data through HTML attributes (directives), uses <template> tags for loops and
conditionals, and provides a complete set of concise markers such as !, #, ., .., :, ::, @
for binding, styling, and events, so you can stay focused on the H5 DOM itself.
Project homepage: https://funlang.org/z-data/
- No virtual DOM, direct DOM manipulation, great performance
- Zero config, zero dependency, no build step
- Extremely small: ~8K minified, < 5K gzipped
- H5 template technology with
for / if / else / use - Templates support multiple root nodes
- Two-way data binding and events
- Embeddable alongside Vue, Alpine and other frameworks
- Rich syntax sugar, friendly to
classandstyle - Built-in Atomic CSS engine — inline styling without Tailwind / build tools
- H5 DOM-centered, build production apps directly in HTML
<script src="//cdn.jsdelivr.net/gh/Funlang/z-data@main/dist/z-data.min.js"></script>
<div z-data="{name:'hello-world.html',items:{i:1,j:2,k:3}}"
#background=`silver`
>
<template for='k:v,i in items' key=k>
<template if=!i>
<div :text=name
:style={fontSize:`200%`}
#color=`#fa0a`
></div>
<div class=t0
:class={t1:true}
.t2
:k=k
>[if !i] i=${i}</div>
</template>
<template else if='k=="j"'>
<div :css.font-size=`120%`>[if k=="j"] k=${k}</div>
</template>
<template else>
<div #font-weight=`bold`>[else] k=${k}</div>
</template>
</template>
</div>Note:
#xxx=...,!xxx=...,..xxx=...etc. are built-in style / class shorthands, documented below.
<script src="//cdn.jsdelivr.net/gh/Funlang/z-data@main/dist/z-data.min.js"></script>Build artifacts live in dist/:
| File | Description |
|---|---|
z-data.min.js |
Core framework for modern browsers |
z-data.js |
Core framework (unminified) |
z-data.min.es2015.js |
ES2015-compatible build |
z-data.min.all.js |
Core + z-json utilities + full Atomic CSS support |
- Online demo (hello-world): https://codepen.io/funlang/pen/NWdOQye
- TodoMVC: https://codepen.io/funlang/pen/ExZOGJy
- Complex data binding: https://codepen.io/funlang/pen/OJWrJep
- Form demo: https://funlang.org/z-data/?url=./test/z-data-form-demo.html
- Component demos: https://codepen.io/funlang/pen/ExZBPJL, https://codepen.io/funlang/pen/RwKzaOo
Prevents flash of unstyled content before initialization, combined with CSS [z-cloak] { display: none }:
z-data
<div z-cloak :z-cloak.attr=false>z-cloak example</div>
<style>
[z-cloak] {
display: none;
}
</style>-
The
z-dataattribute launches a ZData scope:<tag z-data=...></tag>
-
z-datasupports aninitfunction that runs on initialization:<tag z-data=... init=...></tag>
-
The
z-dataexpression can be a function expression:<tag z-data="(args=>{ // some codes ... return { // some props ... } })()"></tag> <tag z-data="args=>{ // some codes ... return { // some props ... } }"></tag>
-
z-data HTML module file: begins with
z-data; the data is whatever a<script>blockreturn {...}s:z-data <tag init=...></tag> <script> // some codes ... return { // some props ... } </script>
-
z-nonestops the ZData scope; all inner nodes are ignored by ZData:<tag z-none> ... </tag>
-
Coexistence with other frameworks: add
x-ignore(or the other framework's own opt-out attribute) to the ZData root tag; addz-noneto other roots to skip ZData processing:<tag z-data=... x-ignore></tag>
<template for='k:v,i in ...' key=...></template>ZData relies on key for diffing. If no key is given, k is used first, otherwise v.
k : v , i are each optional:
<template for='k:v,i in items'></template>
<template for=' v in items'></template>
<template for='k: in items'></template>
<template for=' ,i in items'></template>
<template for='k:v in items'></template>
<template for=' v,i in items'></template>
<template for=items></template><template if=...></template>
<template else if=...></template>
<template else></template>Reuse a template fragment:
<z z-none>
<template id=t1>
<div>template id = t1</div>
</template>
</z>
<template use=#t1></template>: does one-way binding, :: does two-way binding (similar to v-model in other frameworks):
<tag :attr-name1=... ::attr-name2=...></tag>Map to textContent and innerHTML respectively.
Supports [], {} and string forms, merged into the classList in order:
<div :class="['a', {b: true}, 'c d']"></div><tag :class.name1.name2=...></tag>Can be shortened to .name1.name2=.... If a classname ends with - and the return value is not boolean,
the value is appended to the classname; e.g. .p-=1 adds p-1 to the classList.
Supports the :css alias, plus {} and string forms; strings overwrite in order:
<tag :style="{fontSize: '20px'}"></tag>
<tag :css="'color:red'"></tag><tag :style.name.value=...></tag>-
:style.name=value/:css.name=value -
:style.name.value=condition/:css.name.value=... -
Shortened to
..name,#name, and!name=string-value:..width=`100px` #border-width=`4px` !border-width=4px #--a-css-var=`'${theCssVarValue}'`
The value of
..and#is a JS expression; the value of!is treated as a string.
Style shorthand map; ZData.ss returns the full property name for a given abbreviation:
ZData.ss = (s) => AShorthandMap[s] || s;- The value after
: :: @ . .. #is always a JS expression (a variable or a string quoted with `' " ``). - A
${...}inside a non-bound attribute value is parsed into a string, equivalent to`...${...}...`. !behaves like attributes containing${}and compiles to:css.style-name=\string-value``.
:xxx.attr sets a visible attribute; by default it sets a (hidden) property:
<tag :title.attr='...'></tag>:xxx.attr === false produces a boolean attribute. attr shorthand:
:!tag-attr -> :tag-attr.attr
::!tag-attr -> ::tag-attr.attr
:xxx.camel supports camelCase attributes / properties:
::value=propName, ::style.value=propName, ::css.value=propName
propNameis camelCase only; DOM attributes (including style) support the.camelmodifier.- Two-way binding fires on the
changeevent by default; onlyinput type=textalso fires oninput. - Use
.input/.changeto force the corresponding event. - Supports
.trim/.numbermodifiers. - Some properties (e.g.
style.value) do not react automatically when changed; callel.fireChange()in the current execution context.
::checked=opt==this.value
optis a property name in data.
| Marker | Meaning |
|---|---|
:xxx |
property binding |
::xxx |
two-way binding |
.name |
:class.name shorthand |
..name |
:style.name shorthand |
#name |
:style.name shorthand (JS expression) |
!name |
:style.name shorthand (string value) |
@event |
event binding |
:*={...} |
dynamic props |
@*={...} |
dynamic events |
z-d-... |
custom directive |
A signature z-data capability: a Tailwind-like Atomic CSS rules engine embedded in the framework, zero dependency and zero build. The rule definitions and compiler live in the repo
css/directory, and the generated code is merged intoz-data.min.all.js.
Writing lots of CSS classes is tedious, and introducing Tailwind normally requires a build step. z-data
provides a set of abbreviation rules that compile atomic markers such as p1, m2, h3, d-flex,
c#f00, s:p0, hover=b#ff0 into CSS selectors and auto-inject them into
<style id="z-data-ss"> — out of the box, with no build step.
Use the shorthand attributes that map to ZData.ss(name, value) directly on elements — no JS required:
<div p1 hover=p2 a-hover=p3 a^hover=p4 a+hover=p5px a~not-hover=m6pt after=p7 m8 mt9%></div>- An empty property (e.g.
p1) means "style = value". - Post-modifiers such as
hover=...,after=...map to:hover,:afterpseudo-classes. - Pre-modifiers such as
a-hover=...,a^hover=...,a+hover=...,a~not-hover=...map to parent / previous / sibling pseudo-classes. s:p0means a@media (min-width: 640px)media query.
| Group | Rules | Examples |
|---|---|---|
| padding / margin | pm |
p0, m.1, pl2, mt3px, px4%, vars m--varname, py--sm, px--lg |
| max/min, sizing, line-height | h/w/xh/nw/font/lh/gap/brad |
h1, w2, xh100%, nw4%, font12px, lh2, gap2, brad2 |
| left/top/right/bottom | l/t/r/b |
l0, t1, r2pt, b3px |
| opacity/order/z-index | op/ord/z |
op.1, ord2, z3 |
| position/display/... | pos/d/cs/v/ws/us/pe |
pos-absolute, d-block, cs-pointer, ws-nowrap |
| overflow | o/ox/oy |
o-scroll, ox-visible, oy-hidden |
| content/color/background | content/c/b |
content-x, c#000, b#fff |
| media queries | sm/md/lg/xl/2xl/print |
s:p0 |
| state pseudo-classes | `hover/focus/active/after/before/(first | last)` |
| pre-modifier pseudo-classes | a- / a^ / a+ / a~ / not- |
a-hover=m1, parent^active=b#000, elder~hover=b#fff, prev+hover=c#fff |
| groups | bd/bl/bt/br/bb/bg/a/al/font/flex/grid/mask |
bd="style:bold; width:1px; color:red" |
// Inject a block of CSS directly
ZDataStyle.add({ '.foo': { color: '#f00' } });
// Shorthand mapping
ZData.ss('pos'); // "position"
ZData.ss('brad'); // "border-radius"The full rule definitions live in css/atomic-css-rules.txt, the compiler in
css/rule-compiler.fun, and the runtime in css/z-css.js.
@ binds events with modifier support:
<tag @click="doSomething()"></tag>camel camelCase event name: a-camel-name -> aCamelName
prevent preventDefault
stop stopPropagation
debounce debounce mode, optional time: debounce.750ms, debounce.2s, default 250ms
capture capture mode
once run once only
passive passive mode
self tag only
out tag not
window
document
shift
ctrl
alt
meta or cmd
<key> enter, escape, space, f1 etc., details:
https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
alias: space: " ", slash: /, gt: >, eq: =
<button> left, mid, right
:*={...}
:*.attr={...}Example:
<tag :*="{
min: '1',
max: '100'
}"></tag>@*={...}Example:
<tag @*="{
input: 'console.log(event)',
keydown: 'console.log(event)'
}"></tag>z-d-...=...For example z-d-rules-of-validate compiles into a call to rulesOfValidate({e: el, v: val, m: modifiers}):
<tag z-d-rules-of-validate="[
val => !!val || 'Name is required!',
val => val.length > 5 || 'The field need 5 or more characters',
]"></tag>rulesOfValidate({
e: el,
v: val, // attr value of z-d-rules-of-validate
m: modifiers
})The component root element.
The wrapped data object of the component.
Disables DOM observation (for dynamically created z-data nodes you use ZData.loadHTML(), so observing
the DOM is usually not needed):
ZData.nobserve = true;Wrap the data returned by z-data with ZData.proxy() to make it reactive:
return ZData.proxy({
// some reactive data
});Dynamically load html; p is the parent element (default body), before the insert position:
- html containing
<script>must be loaded this way; otherwise:html=...works.
ZData.loadHTML(html, parentElement, beforeElement, args);When you modify a node's style outside z-data and it is two-way bound, call .fireChange() to trigger updates.
Dispatch an event named name to DOM element el, carrying detail.
$emit(el, 'my-event', { any: 'detail' });Debounce helper.
ZData.deb(fn, 250);ZData.start(): manual startup.ZData.on(name, fn)/ZData.call(name, args): pluggable event hooks.
<tag z-comp=...></tag>z-comp can be a ./ relative path or an http(s): resource:
<tag z-comp=./z-comp-2.html></tag>
<tag z-comp=https://funlang.org/zdata/test/z-comp-2.html></tag>It can also be a Promise function that returns component code:
<tag z-comp="load_z_comp('z-comp-2')"></tag>When ZData.get exists, the z:// protocol plugin works. ZData.get is a Promise function that returns
the part after something like fetch(url).then(res => res.text()):
document.addEventListener("DOMContentLoaded", () => setTimeout(() => ZData.get = ...));<z- z-comp=z://table-v1.5.2></z->z-comp can keep or remove its placeholder tag. A z-xxx tag, or one carrying a del attribute, is removed:
<z-comp z-comp=https://funlang.org/zdata/test/z-comp-2.html></z-comp>
<div del z-comp=https://funlang.org/zdata/test/z-comp-2.html></div>Keep the placeholder:
<div z-comp=https://funlang.org/zdata/test/z-comp-2.html></div><tag z-comp=... args=...></tag>Inside the component, use args.xxx to access passed arguments:
<div z-data @mouseover.document=$el.textContent=event.target&&(event.target.title||(event.target.closest('[title]')||{}).title)||''
#color=args.color #background=args.bgcolor !height=100% !padding=8px
></div>z-data.min.all.js additionally bundles a JSON / CSS conversion toolkit (from css/z-css.js):
qs2json(str, dot, defValue, regex): query-string / key-value string to JSON.json2qs(obj, and, eq, dot, cb): JSON to query-string.css2json(str): CSS string to JSON.json2css(obj, level): JSON to CSS, supports nesting (including at-rules such as@media).ZJson.onStringify: serialization hook.
These are great for configuration-driven UI, visual designers, and styling-data serialization.
Online IDE for creating / debugging / previewing z-data code, apps and components:
-
z-data.js,z-data.min.js2017: Chrome 61, Firefox 55, Opera 48, Safari 11, iOS 11, Android 8 -
z-data.min.es2015.js,z-data.min.all.js2016: Chrome 49, Firefox 44, Opera 36, Safari 10, iOS 10, Android 7
z-data/
├── README.md # English docs (default)
├── README-cn.md # Chinese docs
├── hello-world.html # Minimal example
├── src/
│ └── z-data.js # Core framework source
├── css/ # Atomic CSS subsystem
│ ├── README.md # Atomic CSS rules doc
│ ├── atomic-css-rules.txt # Rule definitions (BNF / regex)
│ ├── rule-compiler.fun # Rule compiler (written in funlang)
│ └── z-css.js # Runtime (ZData.ss / ZDataStyle)
├── www/ # z-data studio site assets
├── z-pen/ # Studio build & tooling scripts
├── dist/ # Build artifacts
├── CHANGE.txt # Changelog
├── package.json
└── LICENSE
z-data has no virtual DOM; it operates directly on the H5 DOM and reuses keys in template for, so it
performs very well on large list insert / update scenarios (the project has participated in
js-framework-benchmark comparisons since its early days).
Welcome to join the z-data project. Enjoy!