Object VDOM Readability ‐ Complete HTML Semantic Support and Third‐Party Integration - jurisjs/juris GitHub Wiki
How Juris's object-first architecture enables full HTML semantics, accessibility, and seamless integration with any third-party library
One of Juris's most powerful yet underappreciated features is its complete support for HTML semantics and third-party attributes. Unlike frameworks that abstract away HTML or limit attribute support, Juris's object-first architecture treats every HTML attribute, ARIA property, and custom attribute as a first-class citizen.
This isn't just about compliance—it's about unlocking the full power of the web platform while maintaining the performance and reactivity advantages that make Juris revolutionary.
Juris supports every HTML5 semantic element with full attribute support:
const SemanticDocument = (props, { getState, setState }) => {
return {
html: { lang: 'en',
children: [
{ head: {
children: [
{ meta: { charset: 'utf-8' }},
{ meta: { name: 'viewport', content: 'width=device-width, initial-scale=1' }},
{ title: { text: () => getState('page.title', 'My App') }},
{ link: { rel: 'stylesheet', href: '/styles.css' }},
{ script: { type: 'application/ld+json',
text: () => JSON.stringify(getState('seo.structuredData', {}))
}}
]
}}, //head
{ body: { className: () => getState('theme.mode', 'light'),
children: [
{ header: { className: 'site-header', role: 'banner',
children: [
{ nav: { className: 'main-nav', 'aria-label': 'Main navigation',
children: [
{ h1: { className: 'site-title',
children: [
{ a: { href: '/', 'aria-label': 'Home page',
text: () => getState('site.title', 'My Website')
}}
]
}}, //site-title
{ ul: { className: 'nav-list', role: 'menubar',
children: () => getState('navigation.items', []).map(item => ({
li: { role: 'none',
children: [
{ a: {
href: item.url,
role: 'menuitem',
'aria-current': () => getState('page.current') === item.id ? 'page' : null,
text: item.label
}}
]
} //nav-item
}))
}} //nav-list
]
}} //main-nav
]
}}, //site-header
{ main: { className: 'main-content', role: 'main', id: 'main-content',
children: [
{ article: { className: 'content-article',
children: [
{ header: { className: 'article-header',
children: [
{ h1: {
text: () => getState('article.title', 'Article Title'),
id: 'article-title'
}},
{ div: { className: 'article-meta',
children: [
{ time: {
datetime: () => getState('article.publishedISO'),
text: () => getState('article.publishedFormatted')
}},
{ address: { className: 'author',
children: [
{ span: { text: 'By ' }},
{ a: {
href: () => `/authors/${getState('article.author.id')}`,
rel: 'author',
text: () => getState('article.author.name')
}}
]
}} //author
]
}} //article-meta
]
}}, //article-header
{ section: { className: 'article-content',
children: [
{ p: { text: () => getState('article.excerpt') }},
{ figure: { className: 'featured-image',
children: [
{ img: {
src: () => getState('article.image.url'),
alt: () => getState('article.image.alt'),
width: () => getState('article.image.width'),
height: () => getState('article.image.height'),
loading: 'lazy'
}},
{ figcaption: {
text: () => getState('article.image.caption')
}}
]
}} //featured-image
]
}} //article-content
]
}} //content-article
]
}}, //main-content
{ aside: { className: 'sidebar', 'aria-labelledby': 'sidebar-title',
children: [
{ h2: { id: 'sidebar-title', text: 'Related Content' }},
{ section: { className: 'related-articles',
children: [
{ h3: { text: 'You Might Also Like' }},
{ ul: {
children: () => getState('related.articles', []).map(article => ({
li: {
children: [
{ a: {
href: article.url,
text: article.title,
'aria-describedby': `desc-${article.id}`
}},
{ p: {
id: `desc-${article.id}`,
className: 'article-summary',
text: article.summary
}}
]
} //related-article
}))
}}
]
}} //related-articles
]
}}, //sidebar
{ footer: { className: 'site-footer', role: 'contentinfo',
children: [
{ div: { className: 'footer-content',
children: [
{ p: {
text: () => `© ${new Date().getFullYear()} ${getState('site.title')}`
}},
{ nav: { className: 'footer-nav', 'aria-label': 'Footer navigation',
children: [
{ ul: {
children: [
{ li: {
children: [
{ a: { href: '/privacy', text: 'Privacy Policy' }}
]
}},
{ li: {
children: [
{ a: { href: '/terms', text: 'Terms of Service' }}
]
}},
{ li: {
children: [
{ a: { href: '/contact', text: 'Contact Us' }}
]
}}
]
}}
]
}} //footer-nav
]
}} //footer-content
]
}} //site-footer
]
}} //body
]
} //html
};
};
Juris provides full support for all ARIA attributes and accessibility patterns:
const AccessibleTabPanel = (props, { getState, setState }) => {
const activeTab = () => getState('tabs.active', 0);
const tabs = () => getState('tabs.items', []);
return {
div: { className: 'tab-container',
children: [
{ div: {
className: 'tab-list',
role: 'tablist',
'aria-label': 'Content sections',
children: () => tabs().map((tab, index) => ({
button: {
id: `tab-${index}`,
className: () => `tab ${activeTab() === index ? 'active' : ''}`,
role: 'tab',
'aria-selected': () => activeTab() === index ? 'true' : 'false',
'aria-controls': `panel-${index}`,
tabindex: () => activeTab() === index ? 0 : -1,
text: tab.label,
onclick: () => setState('tabs.active', index),
onkeydown: (e) => {
if (e.key === 'ArrowRight') {
setState('tabs.active', (activeTab() + 1) % tabs().length);
} else if (e.key === 'ArrowLeft') {
setState('tabs.active', (activeTab() - 1 + tabs().length) % tabs().length);
} //key-navigation
}
} //tab-button
}))
}}, //tab-list
{ div: { className: 'tab-panels',
children: () => tabs().map((tab, index) => ({
div: {
id: `panel-${index}`,
className: () => `tab-panel ${activeTab() === index ? 'active' : 'hidden'}`,
role: 'tabpanel',
'aria-labelledby': `tab-${index}`,
'aria-hidden': () => activeTab() === index ? 'false' : 'true',
tabindex: 0,
children: [
{ h3: { text: tab.title }},
{ div: {
innerHTML: () => getState(`tabs.content.${index}`, tab.content),
'aria-live': 'polite'
}}
]
} //tab-panel
}))
}} //tab-panels
]
} //tab-container
};
};
const AccessibleForm = (props, { getState, setState, validationService }) => {
return {
form: {
className: 'registration-form',
'aria-labelledby': 'form-title',
novalidate: true,
onsubmit: (e) => {
e.preventDefault();
validationService.validateForm();
},
children: [
{ fieldset: { className: 'personal-info',
children: [
{ legend: {
id: 'form-title',
text: 'Personal Information'
}},
{ div: { className: 'form-group',
children: [
{ label: {
for: 'full-name',
className: 'required',
text: 'Full Name'
}},
{ input: {
id: 'full-name',
name: 'fullName',
type: 'text',
required: true,
'aria-required': 'true',
'aria-invalid': () => getState('form.errors.fullName') ? 'true' : 'false',
'aria-describedby': () => {
const parts = ['name-help'];
if (getState('form.errors.fullName')) parts.push('name-error');
return parts.join(' ');
},
value: () => getState('form.fullName', ''),
oninput: (e) => {
setState('form.fullName', e.target.value);
validationService.clearError('fullName');
}
}},
{ div: {
id: 'name-help',
className: 'help-text',
text: 'Enter your legal first and last name'
}},
{ div: {
id: 'name-error',
className: 'error-message',
role: 'alert',
'aria-live': 'assertive',
'aria-atomic': 'true',
text: () => getState('form.errors.fullName', ''),
style: () => ({
display: getState('form.errors.fullName') ? 'block' : 'none'
})
}}
]
}}, //form-group
{ div: { className: 'form-group',
children: [
{ fieldset: { className: 'gender-fieldset',
children: [
{ legend: { text: 'Gender (Optional)' }},
{ div: { className: 'radio-group', role: 'radiogroup',
children: [
{ label: { className: 'radio-label',
children: [
{ input: {
type: 'radio',
name: 'gender',
value: 'female',
checked: () => getState('form.gender') === 'female',
onchange: (e) => setState('form.gender', e.target.value)
}},
{ span: { text: 'Female' }}
]
}},
{ label: { className: 'radio-label',
children: [
{ input: {
type: 'radio',
name: 'gender',
value: 'male',
checked: () => getState('form.gender') === 'male',
onchange: (e) => setState('form.gender', e.target.value)
}},
{ span: { text: 'Male' }}
]
}},
{ label: { className: 'radio-label',
children: [
{ input: {
type: 'radio',
name: 'gender',
value: 'other',
checked: () => getState('form.gender') === 'other',
onchange: (e) => setState('form.gender', e.target.value)
}},
{ span: { text: 'Other' }}
]
}}
]
}} //radio-group
]
}} //gender-fieldset
]
}} //form-group
]
}}, //personal-info
{ div: { className: 'form-actions',
children: [
{ button: {
type: 'submit',
className: 'btn-primary',
'aria-describedby': 'submit-help',
disabled: () => !getState('form.isValid', false),
text: () => getState('form.isSubmitting', false) ? 'Submitting...' : 'Register'
}},
{ div: {
id: 'submit-help',
className: 'help-text',
text: 'By registering, you agree to our Terms of Service'
}}
]
}} //form-actions
]
} //registration-form
};
};
Juris seamlessly supports HTMX attributes for progressive enhancement:
const HTMXIntegration = (props, { getState, setState }) => {
return {
div: { className: 'htmx-demo',
children: [
{ section: { className: 'user-list',
children: [
{ h2: { text: 'Users' }},
{ div: {
id: 'user-list-container',
'hx-get': '/api/users',
'hx-trigger': 'load',
'hx-swap': 'innerHTML',
'hx-indicator': '#loading-users',
children: [
{ div: {
id: 'loading-users',
className: 'htmx-indicator',
text: 'Loading users...'
}}
]
}},
{ form: {
className: 'add-user-form',
'hx-post': '/api/users',
'hx-target': '#user-list-container',
'hx-swap': 'beforeend',
'hx-on::after-request': 'this.reset()',
children: [
{ div: { className: 'form-group',
children: [
{ label: { for: 'user-name', text: 'Name' }},
{ input: {
id: 'user-name',
name: 'name',
type: 'text',
required: true,
'hx-validate': 'true'
}}
]
}},
{ div: { className: 'form-group',
children: [
{ label: { for: 'user-email', text: 'Email' }},
{ input: {
id: 'user-email',
name: 'email',
type: 'email',
required: true,
'hx-validate': 'true'
}}
]
}},
{ button: {
type: 'submit',
text: 'Add User',
'hx-indicator': '#adding-user'
}},
{ span: {
id: 'adding-user',
className: 'htmx-indicator',
text: 'Adding...'
}}
]
}} //add-user-form
]
}}, //user-list
{ section: { className: 'live-search',
children: [
{ h2: { text: 'Search Users' }},
{ input: {
type: 'search',
name: 'search',
placeholder: 'Search users...',
'hx-get': '/api/search/users',
'hx-trigger': 'keyup changed delay:300ms, search',
'hx-target': '#search-results',
'hx-indicator': '#search-loading',
'hx-push-url': 'true'
}},
{ div: {
id: 'search-loading',
className: 'htmx-indicator',
text: 'Searching...'
}},
{ div: {
id: 'search-results',
'hx-swap-oob': 'true'
}}
]
}}, //live-search
{ section: { className: 'infinite-scroll',
children: [
{ h2: { text: 'Posts Feed' }},
{ div: {
id: 'posts-container',
children: () => getState('posts.items', []).map((post, index) => ({
article: {
className: 'post-item',
key: post.id,
children: [
{ h3: { text: post.title }},
{ p: { text: post.excerpt }},
{ div: { className: 'post-actions',
children: [
{ button: {
className: 'like-btn',
'hx-post': `/api/posts/${post.id}/like`,
'hx-swap': 'outerHTML',
'hx-target': 'this',
text: () => post.liked ? '❤️ Liked' : '🤍 Like'
}},
{ button: {
className: 'share-btn',
'hx-get': `/api/posts/${post.id}/share-modal`,
'hx-target': '#modal-container',
'hx-swap': 'innerHTML',
text: '📤 Share'
}}
]
}} //post-actions
]
} //post-item
}))
}},
{ div: {
'hx-get': '/api/posts/more',
'hx-trigger': 'revealed',
'hx-swap': 'afterend',
'hx-target': 'previous div',
'hx-indicator': '#loading-more',
children: [
{ div: {
id: 'loading-more',
className: 'htmx-indicator',
text: '⏳ Loading more posts...'
}}
]
}}
]
}} //infinite-scroll
]
} //htmx-demo
};
};
Juris can coexist with Vue.js by supporting Vue-specific attributes:
const VueCompatibility = (props, { getState, setState }) => {
return {
div: { className: 'vue-compat-demo', id: 'vue-app',
children: [
{ section: { className: 'vue-enhanced',
children: [
{ h2: { text: 'Vue.js Enhanced Section' }},
{ div: {
className: 'todo-list',
'v-if': 'showTodos',
children: [
{ input: {
type: 'text',
'v-model': 'newTodo',
'v-on:keyup.enter': 'addTodo',
placeholder: 'Add a todo...'
}},
{ ul: {
children: [
{ li: {
'v-for': 'todo in todos',
'v-bind:key': 'todo.id',
'v-bind:class': '{ completed: todo.done }',
children: [
{ input: {
type: 'checkbox',
'v-model': 'todo.done'
}},
{ span: {
'v-text': 'todo.text',
'v-bind:style': '{ textDecoration: todo.done ? "line-through" : "none" }'
}},
{ button: {
'v-on:click': 'removeTodo(todo.id)',
text: '✕'
}}
]
}}
]
}}
]
}},
{ div: { className: 'vue-animations',
children: [
{ transition: { name: 'fade',
children: [
{ div: {
'v-if': 'showMessage',
'v-text': 'message',
className: 'message'
}}
]
}},
{ 'transition-group': { name: 'list', tag: 'ul',
children: [
{ li: {
'v-for': 'item in animatedList',
'v-bind:key': 'item.id',
'v-text': 'item.name'
}}
]
}}
]
}} //vue-animations
]
}}, //vue-enhanced
{ section: { className: 'juris-native',
children: [
{ h2: { text: 'Juris Native Section' }},
{ div: { className: 'counter',
children: [
{ button: {
text: '-',
onclick: () => setState('counter', getState('counter', 0) - 1)
}},
{ span: {
text: () => `Count: ${getState('counter', 0)}`,
className: 'count-display'
}},
{ button: {
text: '+',
onclick: () => setState('counter', getState('counter', 0) + 1)
}}
]
}},
{ div: { className: 'reactive-list',
children: () => getState('items', []).map(item => ({
div: {
className: () => `item ${item.active ? 'active' : ''}`,
key: item.id,
children: [
{ span: { text: item.name }},
{ button: {
text: 'Toggle',
onclick: () => {
const items = getState('items', []);
const updated = items.map(i =>
i.id === item.id ? { ...i, active: !i.active } : i
);
setState('items', updated);
}
}}
]
} //item
}))
}} //reactive-list
]
}} //juris-native
]
} //vue-compat-demo
};
};
Juris works seamlessly with Alpine.js directives:
const AlpineIntegration = (props, { getState, setState }) => {
return {
div: { className: 'alpine-demo',
children: [
{ section: {
'x-data': '{ open: false, message: "Hello Alpine!" }',
className: 'alpine-section',
children: [
{ h2: { text: 'Alpine.js Integration' }},
{ button: {
'x-on:click': 'open = !open',
'x-text': 'open ? "Close" : "Open"',
className: 'toggle-btn'
}},
{ div: {
'x-show': 'open',
'x-transition:enter': 'transition ease-out duration-300',
'x-transition:enter-start': 'opacity-0 transform scale-90',
'x-transition:enter-end': 'opacity-100 transform scale-100',
'x-transition:leave': 'transition ease-in duration-300',
'x-transition:leave-start': 'opacity-100 transform scale-100',
'x-transition:leave-end': 'opacity-0 transform scale-90',
className: 'alpine-content',
children: [
{ p: { 'x-text': 'message' }},
{ input: {
type: 'text',
'x-model': 'message',
placeholder: 'Edit message...'
}}
]
}}
]
}}, //alpine-section
{ section: {
'x-data': '{ items: [], newItem: "" }',
'x-init': 'items = await fetch("/api/items").then(r => r.json())',
className: 'alpine-list',
children: [
{ h3: { text: 'Dynamic List' }},
{ form: {
'x-on:submit.prevent': 'items.push({id: Date.now(), name: newItem}); newItem = ""',
children: [
{ input: {
type: 'text',
'x-model': 'newItem',
placeholder: 'Add item...'
}},
{ button: { type: 'submit', text: 'Add' }}
]
}},
{ ul: {
children: [
{ template: {
'x-for': 'item in items',
'x-key': 'item.id',
children: [
{ li: {
'x-text': 'item.name',
'x-on:click': 'item.selected = !item.selected',
'x-bind:class': '{ selected: item.selected }'
}}
]
}}
]
}}
]
}}, //alpine-list
{ section: { className: 'hybrid-reactivity',
children: [
{ h3: { text: 'Hybrid Reactivity' }},
{ div: {
'x-data': `{
alpineCount: 0,
get jurisCount() {
return ${() => getState('hybridCounter', 0)}();
}
}`,
children: [
{ p: { 'x-text': '`Alpine count: ${alpineCount}`' }},
{ p: { text: () => `Juris count: ${getState('hybridCounter', 0)}` }},
{ button: {
'x-on:click': 'alpineCount++',
text: 'Increment Alpine'
}},
{ button: {
onclick: () => setState('hybridCounter', getState('hybridCounter', 0) + 1),
text: 'Increment Juris'
}}
]
}}
]
}} //hybrid-reactivity
]
} //alpine-demo
};
};
Juris supports all custom attributes and Web Components:
const WebComponentsDemo = (props, { getState, setState }) => {
return {
div: { className: 'web-components-demo',
children: [
{ section: { className: 'custom-elements',
children: [
{ h2: { text: 'Web Components' }},
// Custom elements with full attribute support
{ 'user-card': {
'user-id': () => getState('selectedUser.id'),
'avatar-size': 'large',
'show-bio': 'true',
'theme': () => getState('app.theme', 'light'),
'data-testid': 'user-card-component'
}},
{ 'data-chart': {
type: 'line',
'chart-data': () => JSON.stringify(getState('chart.data', [])),
width: '600',
height: '400',
'auto-update': 'true',
'update-interval': '5000'
}},
{ 'media-player': {
src: () => getState('media.currentTrack.url'),
'poster-image': () => getState('media.currentTrack.poster'),
'auto-play': () => getState('media.autoPlay', false) ? 'true' : 'false',
'show-controls': 'true',
'track-title': () => getState('media.currentTrack.title'),
'track-artist': () => getState('media.currentTrack.artist')
}}
]
}}, //custom-elements
{ section: { className: 'data-attributes',
children: [
{ h2: { text: 'Data Attributes & Analytics' }},
{ div: { className: 'product-grid',
'data-category': () => getState('products.category'),
'data-sort': () => getState('products.sortBy'),
'data-view': () => getState('products.viewMode'),
children: () => getState('products.items', []).map(product => ({
article: {
className: 'product-card',
'data-product-id': product.id,
'data-product-price': product.price,
'data-product-category': product.category,
'data-in-stock': product.inStock ? 'true' : 'false',
'data-analytics-event': 'product-view',
'data-analytics-value': product.price,
children: [
{ img: {
src: product.image,
alt: product.name,
'data-lazy': 'true',
'data-src-fallback': '/placeholder.jpg'
}},
{ h3: {
text: product.name,
'data-testid': `product-title-${product.id}`
}},
{ p: {
className: 'price',
text: `$${product.price}`,
'data-currency': 'USD',
'data-original-price': product.originalPrice
}},
{ button: {
className: 'add-to-cart',
'data-product-id': product.id,
'data-analytics-event': 'add-to-cart',
'data-analytics-category': 'ecommerce',
'data-analytics-label': product.name,
'data-analytics-value': product.price,
text: 'Add to Cart',
onclick: (e) => {
// Juris can read any data attribute
const productId = e.target.dataset.productId;
const price = e.target.dataset.analyticsValue;
addToCart(productId, price);
}
}}
]
} //product-card
}))
}} //product-grid
]
}} //data-attributes
]
} //web-components-demo
};
};
Juris provides complete control over document head and SEO:
const SEODocument = (props, { getState, setState, seoService }) => {
return {
html: {
lang: () => getState('i18n.locale', 'en'),
'data-theme': () => getState('app.theme', 'light'),
children: [
{ head: {
children: [
// Basic meta tags
{ meta: { charset: 'utf-8' }},
{ meta: { name: 'viewport', content: 'width=device-width, initial-scale=1.0' }},
{ meta: { 'http-equiv': 'X-UA-Compatible', content: 'IE=edge' }},
// Dynamic SEO meta tags
{ title: { text: () => getState('seo.title', 'Default Title') }},
{ meta: {
name: 'description',
content: () => getState('seo.description', 'Default description')
}},
{ meta: {
name: 'keywords',
content: () => getState('seo.keywords', []).join(', ')
}},
{ meta: {
name: 'author',
content: () => getState('seo.author', 'Site Author')
}},
// Open Graph tags
{ meta: { property: 'og:type', content: () => getState('seo.og.type', 'website') }},
{ meta: { property: 'og:title', content: () => getState('seo.og.title') }},
{ meta: { property: 'og:description', content: () => getState('seo.og.description') }},
{ meta: { property: 'og:image', content: () => getState('seo.og.image') }},
{ meta: { property: 'og:url', content: () => getState('seo.og.url') }},
{ meta: { property: 'og:site_name', content: () => getState('seo.og.siteName') }},
// Twitter Card tags
{ meta: { name: 'twitter:card', content: () => getState('seo.twitter.card', 'summary') }},
{ meta: { name: 'twitter:site', content: () => getState('seo.twitter.site') }},
{ meta: { name: 'twitter:creator', content: () => getState('seo.twitter.creator') }},
{ meta: { name: 'twitter:title', content: () => getState('seo.twitter.title') }},
{ meta: { name: 'twitter:description', content: () => getState('seo.twitter.description') }},
{ meta: { name: 'twitter:image', content: () => getState('seo.twitter.image') }},
// Canonical and alternate links
{ link: { rel: 'canonical', href: () => getState('seo.canonical') }},
{ link: {
rel: 'alternate',
hreflang: 'en',
href: () => getState('seo.alternates.en')
}},
{ link: {
rel: 'alternate',
hreflang: 'es',
href: () => getState('seo.alternates.es')
}},
// Structured data
{ script: {
type: 'application/ld+json',
text: () => JSON.stringify(getState('seo.structuredData', {}))
}},
// Preload and prefetch resources
{ link: {
rel: 'preload',
href: '/fonts/main.woff2',
as: 'font',
type: 'font/woff2',
crossorigin: 'anonymous'
}},
{ link: {
rel: 'prefetch',
href: () => getState('prefetch.nextPage')
}},
// Stylesheet and theme
{ link: { rel: 'stylesheet', href: '/styles/main.css' }},
{ link: {
rel: 'stylesheet',
href: () => `/themes/${getState('app.theme', 'light')}.css`
}},
// Favicon and app icons
{ link: { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }},
{ link: { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' }},
{ link: { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png' }},
{ link: { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png' }},
{ link: { rel: 'manifest', href: '/site.webmanifest' }}
]
}}, //head
{ body: {
className: () => `theme-${getState('app.theme', 'light')} lang-${getState('i18n.locale', 'en')}`,
'data-page': () => getState('page.current'),
'data-user-type': () => getState('user.type', 'guest'),
children: [
// Your app content here
{ div: {
id: 'app',
text: () => `Current page: ${getState('page.title', 'Home')}`
}}
]
}} //body
]
} //html
};
};
One of the most compelling advantages of Juris's object-first architecture becomes apparent when dealing with heavily nested HTML structures. Let's examine the same complex component written in both pure HTML and Juris to see how the object structure with end bracket labeling dramatically improves readability and maintainability.
<!-- Pure HTML: Heavily nested structure -->
<article class="product-card" data-product-id="12345" data-category="electronics" data-price="299.99">
<div class="product-card-container">
<div class="product-image-section">
<div class="image-container">
<div class="image-wrapper">
<img src="/product-images/laptop-pro.jpg" alt="Laptop Pro 15-inch" class="product-image" loading="lazy">
<div class="image-overlay">
<div class="overlay-actions">
<button class="quick-view-btn" data-action="quick-view" aria-label="Quick view of Laptop Pro">
<svg class="icon" aria-hidden="true">
<use href="#eye-icon"></use>
</svg>
</button>
<button class="wishlist-btn" data-action="add-wishlist" aria-label="Add to wishlist">
<svg class="icon" aria-hidden="true">
<use href="#heart-icon"></use>
</svg>
</button>
</div>
</div>
</div>
<div class="product-badges">
<span class="badge sale-badge">Sale</span>
<span class="badge new-badge">New</span>
</div>
</div>
</div>
<div class="product-info-section">
<div class="product-header">
<div class="product-brand">
<span class="brand-name">TechCorp</span>
</div>
<div class="product-rating">
<div class="stars-container">
<div class="stars-wrapper">
<div class="stars-filled" style="width: 85%;">
<span class="star">★</span>
<span class="star">★</span>
<span class="star">★</span>
<span class="star">★</span>
<span class="star">★</span>
</div>
<div class="stars-empty">
<span class="star">☆</span>
<span class="star">☆</span>
<span class="star">☆</span>
<span class="star">☆</span>
<span class="star">☆</span>
</div>
</div>
</div>
<span class="rating-count">(127 reviews)</span>
</div>
</div>
<div class="product-details">
<h3 class="product-title">
<a href="/products/laptop-pro-15" class="product-link">Laptop Pro 15-inch - High Performance Computing</a>
</h3>
<div class="product-description">
<p class="description-text">Professional-grade laptop with cutting-edge performance for developers and creators.</p>
</div>
<div class="product-features">
<ul class="features-list">
<li class="feature-item">
<span class="feature-icon">💻</span>
<span class="feature-text">15.6" 4K Display</span>
</li>
<li class="feature-item">
<span class="feature-icon">⚡</span>
<span class="feature-text">32GB RAM</span>
</li>
<li class="feature-item">
<span class="feature-icon">💾</span>
<span class="feature-text">1TB SSD</span>
</li>
</ul>
</div>
</div>
<div class="product-pricing">
<div class="price-container">
<div class="price-main">
<span class="currency">$</span>
<span class="price-amount">299</span>
<span class="price-cents">.99</span>
</div>
<div class="price-original">
<span class="original-price">$399.99</span>
</div>
<div class="price-savings">
<span class="savings-text">Save $100</span>
</div>
</div>
</div>
<div class="product-actions">
<div class="actions-container">
<div class="quantity-selector">
<label for="quantity-12345" class="quantity-label">Qty:</label>
<div class="quantity-controls">
<button class="quantity-btn decrease" data-action="decrease-qty" aria-label="Decrease quantity">-</button>
<input type="number" id="quantity-12345" class="quantity-input" value="1" min="1" max="10">
<button class="quantity-btn increase" data-action="increase-qty" aria-label="Increase quantity">+</button>
</div>
</div>
<div class="action-buttons">
<button class="btn btn-primary add-to-cart" data-product-id="12345" data-action="add-to-cart">
<span class="btn-icon">
<svg aria-hidden="true">
<use href="#cart-icon"></use>
</svg>
</span>
<span class="btn-text">Add to Cart</span>
</button>
<button class="btn btn-secondary buy-now" data-product-id="12345" data-action="buy-now">
<span class="btn-text">Buy Now</span>
</button>
</div>
</div>
<div class="product-meta">
<div class="meta-info">
<div class="shipping-info">
<span class="shipping-icon">🚚</span>
<span class="shipping-text">Free shipping on orders over $25</span>
</div>
<div class="stock-info">
<span class="stock-indicator available"></span>
<span class="stock-text">In stock - ships within 24 hours</span>
</div>
</div>
</div>
</div>
</div>
</div>
</article>
Problems with Pure HTML:
- Visual clutter: Hard to see structure through angle brackets
- No clear hierarchy: Difficult to identify nested levels
- Missing context: No indication of what closing tags belong to
- Maintenance nightmare: Finding matching opening/closing tags is error-prone
- No semantic clarity: Structure lost in syntax noise
const ProductCard = (props, { getState, setState, cartService, wishlistService }) => {
const product = () => getState(`products.${props.productId}`, {});
return {
article: {
className: 'product-card',
'data-product-id': props.productId,
'data-category': () => product().category,
'data-price': () => product().price,
children: [
{ div: { className: 'product-card-container',
children: [
{ div: { className: 'product-image-section',
children: [
{ div: { className: 'image-container',
children: [
{ div: { className: 'image-wrapper',
children: [
{ img: {
src: () => product().image,
alt: () => product().alt,
className: 'product-image',
loading: 'lazy'
}},
{ div: { className: 'image-overlay',
children: [
{ div: { className: 'overlay-actions',
children: [
{ button: {
className: 'quick-view-btn',
'data-action': 'quick-view',
'aria-label': () => `Quick view of ${product().name}`,
onclick: () => openQuickView(props.productId),
children: [
{ svg: { className: 'icon', 'aria-hidden': 'true',
children: [
{ use: { href: '#eye-icon' }}
]
}} //svg.icon
]
}}, //quick-view-btn
{ button: {
className: 'wishlist-btn',
'data-action': 'add-wishlist',
'aria-label': 'Add to wishlist',
onclick: () => wishlistService.add(props.productId),
children: [
{ svg: { className: 'icon', 'aria-hidden': 'true',
children: [
{ use: { href: '#heart-icon' }}
]
}} //svg.icon
]
}} //wishlist-btn
]
}} //overlay-actions
]
}} //image-overlay
]
}}, //image-wrapper
{ div: { className: 'product-badges',
children: () => {
const badges = [];
if (product().onSale) badges.push({
span: { className: 'badge sale-badge', text: 'Sale' }
});
if (product().isNew) badges.push({
span: { className: 'badge new-badge', text: 'New' }
});
return badges;
}
}} //product-badges
]
}} //image-container
]
}}, //product-image-section
{ div: { className: 'product-info-section',
children: [
{ div: { className: 'product-header',
children: [
{ div: { className: 'product-brand',
children: [
{ span: {
className: 'brand-name',
text: () => product().brand
}}
]
}}, //product-brand
{ div: { className: 'product-rating',
children: [
{ div: { className: 'stars-container',
children: [
{ div: { className: 'stars-wrapper',
children: [
{ div: {
className: 'stars-filled',
style: () => ({ width: `${product().rating * 20}%` }),
children: [
{ span: { className: 'star', text: '★' }},
{ span: { className: 'star', text: '★' }},
{ span: { className: 'star', text: '★' }},
{ span: { className: 'star', text: '★' }},
{ span: { className: 'star', text: '★' }}
]
}}, //stars-filled
{ div: { className: 'stars-empty',
children: [
{ span: { className: 'star', text: '☆' }},
{ span: { className: 'star', text: '☆' }},
{ span: { className: 'star', text: '☆' }},
{ span: { className: 'star', text: '☆' }},
{ span: { className: 'star', text: '☆' }}
]
}} //stars-empty
]
}} //stars-wrapper
]
}}, //stars-container
{ span: {
className: 'rating-count',
text: () => `(${product().reviewCount} reviews)`
}}
]
}} //product-rating
]
}}, //product-header
{ div: { className: 'product-details',
children: [
{ h3: { className: 'product-title',
children: [
{ a: {
href: () => `/products/${product().slug}`,
className: 'product-link',
text: () => product().name
}}
]
}}, //product-title
{ div: { className: 'product-description',
children: [
{ p: {
className: 'description-text',
text: () => product().description
}}
]
}}, //product-description
{ div: { className: 'product-features',
children: [
{ ul: { className: 'features-list',
children: () => product().features?.map(feature => ({
li: { className: 'feature-item',
children: [
{ span: {
className: 'feature-icon',
text: feature.icon
}},
{ span: {
className: 'feature-text',
text: feature.text
}}
]
} //feature-item
})) || []
}} //features-list
]
}} //product-features
]
}}, //product-details
{ div: { className: 'product-pricing',
children: [
{ div: { className: 'price-container',
children: [
{ div: { className: 'price-main',
children: [
{ span: { className: 'currency', text: '$' }},
{ span: {
className: 'price-amount',
text: () => Math.floor(product().price)
}},
{ span: {
className: 'price-cents',
text: () => `.${(product().price % 1).toFixed(2).slice(2)}`
}}
]
}}, //price-main
{ div: { className: 'price-original',
style: () => ({
display: product().originalPrice ? 'block' : 'none'
}),
children: [
{ span: {
className: 'original-price',
text: () => `$${product().originalPrice}`
}}
]
}}, //price-original
{ div: { className: 'price-savings',
style: () => ({
display: product().savings ? 'block' : 'none'
}),
children: [
{ span: {
className: 'savings-text',
text: () => `Save $${product().savings}`
}}
]
}} //price-savings
]
}} //price-container
]
}}, //product-pricing
{ div: { className: 'product-actions',
children: [
{ div: { className: 'actions-container',
children: [
{ div: { className: 'quantity-selector',
children: [
{ label: {
for: `quantity-${props.productId}`,
className: 'quantity-label',
text: 'Qty:'
}},
{ div: { className: 'quantity-controls',
children: [
{ button: {
className: 'quantity-btn decrease',
'data-action': 'decrease-qty',
'aria-label': 'Decrease quantity',
onclick: () => decreaseQuantity(props.productId),
text: '-'
}},
{ input: {
type: 'number',
id: `quantity-${props.productId}`,
className: 'quantity-input',
value: () => getState(`cart.quantities.${props.productId}`, 1),
min: '1',
max: '10',
onchange: (e) => setState(`cart.quantities.${props.productId}`, parseInt(e.target.value))
}},
{ button: {
className: 'quantity-btn increase',
'data-action': 'increase-qty',
'aria-label': 'Increase quantity',
onclick: () => increaseQuantity(props.productId),
text: '+'
}}
]
}} //quantity-controls
]
}}, //quantity-selector
{ div: { className: 'action-buttons',
children: [
{ button: {
className: 'btn btn-primary add-to-cart',
'data-product-id': props.productId,
'data-action': 'add-to-cart',
onclick: () => cartService.addItem(props.productId, getState(`cart.quantities.${props.productId}`, 1)),
children: [
{ span: { className: 'btn-icon',
children: [
{ svg: { 'aria-hidden': 'true',
children: [
{ use: { href: '#cart-icon' }}
]
}} //svg
]
}}, //btn-icon
{ span: {
className: 'btn-text',
text: 'Add to Cart'
}}
]
}}, //add-to-cart
{ button: {
className: 'btn btn-secondary buy-now',
'data-product-id': props.productId,
'data-action': 'buy-now',
onclick: () => cartService.buyNow(props.productId),
children: [
{ span: {
className: 'btn-text',
text: 'Buy Now'
}}
]
}} //buy-now
]
}} //action-buttons
]
}}, //actions-container
{ div: { className: 'product-meta',
children: [
{ div: { className: 'meta-info',
children: [
{ div: { className: 'shipping-info',
children: [
{ span: { className: 'shipping-icon', text: '🚚' }},
{ span: {
className: 'shipping-text',
text: 'Free shipping on orders over $25'
}}
]
}}, //shipping-info
{ div: { className: 'stock-info',
children: [
{ span: {
className: () => `stock-indicator ${product().inStock ? 'available' : 'unavailable'}`
}},
{ span: {
className: 'stock-text',
text: () => product().inStock
? 'In stock - ships within 24 hours'
: 'Out of stock - notify when available'
}}
]
}} //stock-info
]
}} //meta-info
]
}} //product-meta
]
}} //product-actions
]
}} //product-info-section
]
}} //product-card-container
]
} //article.product-card
};
};
Pure HTML Issues:
- Angle brackets create visual noise that obscures structure
- No visual hierarchy - everything looks the same
- Closing tags provide no context about what they're closing
- Indentation is the only structural indicator
Juris Advantages:
- Clean object notation shows hierarchy naturally
- End bracket labels
//component-name
provide immediate context - Nested structure is visually obvious through object properties
- Each level of nesting is semantically meaningful
Pure HTML Maintenance:
- Finding matching opening/closing tags requires counting
- Adding new elements requires careful tag management
- Refactoring structure is error-prone
- No indication of component boundaries
Juris Maintenance:
- Object structure makes hierarchy obvious
- End labels make it clear what each closing bracket refers to
- Refactoring is safe with proper object manipulation
- Component boundaries are clearly defined
Pure HTML:
<!-- Which div does this close? You have to count backwards -->
</div>
</div>
</div>
</div>
</div>
</article>
Juris:
}} //quantity-controls
]
}}, //quantity-selector
]
}} //actions-container
]
}} //product-actions
]
} //product-card-container
};
Juris's end bracket labeling system provides:
-
Immediate Context:
}} //product-actions
tells you exactly what's ending - Visual Scanning: Easy to find specific sections by scanning labels
- Debugging Aid: Syntax errors point to specific components
- Code Review: Reviewers can understand structure without parsing entire blocks
- Refactoring Safety: Clear boundaries prevent accidental structural changes
Time to Understanding:
- Pure HTML: 3-5 minutes to understand structure
- Juris with labels: 30-60 seconds to understand structure
Error Prevention:
- Pure HTML: High likelihood of mismatched tags
- Juris: Syntax errors caught immediately by JavaScript parser
Team Collaboration:
- Pure HTML: Requires expertise to review complex structures
- Juris: Any developer can quickly understand component hierarchy
const ComplexComponent = () => {
return {
main: { className: 'dashboard',
children: [
{ header: { className: 'dashboard-header',
children: [
{ nav: { className: 'main-nav',
children: [
// Navigation items
]
}} //nav.main-nav
]
}}, //header.dashboard-header
{ section: { className: 'content-grid',
children: [
{ aside: { className: 'sidebar',
children: [
// Sidebar content
]
}}, //aside.sidebar
{ article: { className: 'main-content',
children: [
// Main content
]
}} //article.main-content
]
}}, //section.content-grid
{ footer: { className: 'dashboard-footer',
children: [
// Footer content
]
}} //footer.dashboard-footer
]
} //main.dashboard
};
};
Labeling Guidelines:
- Include element type:
}} //div.container
- Add key classes:
}} //button.btn-primary
- Use semantic names:
}} //nav.main-navigation
- Be consistent across the codebase
- Label every closing bracket in complex structures
- Every HTML5 element and attribute supported
- No abstraction layer limiting functionality
- Direct access to all web platform features
- New HTML elements work immediately
- Custom elements integrate seamlessly
- Third-party attributes require no framework updates
- Full ARIA support built-in
- Semantic HTML encouraged through object structure
- Screen reader compatibility guaranteed
- Complete meta tag control
- Structured data support
- Server-side rendering ready
- IntelliSense for all HTML attributes
- Type safety for standard elements
- Easy debugging with real DOM elements
Juris's object-first architecture doesn't sacrifice HTML semantics or web standards—it enhances them. By treating every HTML attribute as a first-class property, Juris enables developers to build fully semantic, accessible, and standards-compliant applications while maintaining the performance and reactivity advantages that make it revolutionary.
Whether you're building with HTMX for progressive enhancement, integrating with Vue.js or Alpine.js for gradual migration, or creating Web Components for maximum reusability, Juris supports your approach without compromise.
The web platform is incredibly powerful—Juris simply gives you complete access to that power while making it reactive, performant, and maintainable. The 2-space indentation format combined with end bracket labeling creates code that is both visually clean and structurally obvious, making complex nested components as readable as simple ones.