Introducing the first framework with truly granular, attribute-level reactivity
The Revolution You Haven't Heard About
While the JavaScript community debates the merits of React hooks, Vue's Composition API, and Svelte's compile-time optimizations, a quietly revolutionary framework has emerged with a completely different approach: reactive attributes.
Meet Juris.js - the first and only framework where every single HTML attribute can be a live, reactive expression.
What Are Reactive Attributes?
Imagine if every property of every DOM element could automatically update when your application state changes. Not just the component re-rendering, but the individual attributes themselves becoming reactive:
{input: {
type: 'password',
className: () => `form-input ${getState('errors.password') ? 'error' : ''}`,
value: () => getState('auth.password', ''),
placeholder: () => getState('auth.mode') === 'change' ? 'New password' : 'Password',
disabled: () => getState('auth.loading'),
style: () => ({
opacity: getState('ui.visible') ? 1 : 0.5,
borderColor: getState('validation.status') === 'error' ? 'red' : 'blue'
}),
maxLength: () => getState('settings.passwordMaxLength', 50),
tabIndex: () => getState('ui.focusMode') ? 0 : -1
}}
Every single attribute in that example is independently reactive. When auth.loading
changes, only the disabled
attribute updates. When ui.visible
changes, only the opacity style updates. This is granular reactivity at a level no other framework achieves.
The Problem With Current Frameworks
React: Component-Level Reactivity
function PasswordInput() {
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState({});
const [mode, setMode] = useState('signin');
// Entire component re-renders when ANY state changes
return (
<input
type="password"
className={`form-input ${errors.password ? 'error' : ''}`}
placeholder={mode === 'change' ? 'New password' : 'Password'}
disabled={loading}
/>
);
}
Problem: Change loading
and the entire component re-renders, even though only the disabled
attribute needs to update.
Vue: Better, But Still Component-Focused
<template>
<input
type="password"
:class="`form-input ${errors.password ? 'error' : ''}`"
:placeholder="mode === 'change' ? 'New password' : 'Password'"
:disabled="loading"
/>
</template>
Problem: While Vue optimizes better than React, it's still thinking in terms of component updates, not attribute updates.
Svelte: Compile-Time Optimization
<input
type="password"
class="form-input {errors.password ? 'error' : ''}"
placeholder={mode === 'change' ? 'New password' : 'Password'}
disabled={loading}
/>
Problem: Better performance through compilation, but still no true attribute-level reactivity.
How Juris Reactive Attributes Work
1. Automatic Dependency Tracking
When you write a reactive attribute function, Juris automatically tracks which state it accesses:
// This function automatically becomes dependent on 'user.name' and 'user.status'
text: () => `${getState('user.name')} (${getState('user.status')})`
Behind the scenes, Juris uses a brilliant dependency tracking system:
_createReactiveUpdate(element, updateFn, subscriptions) {
const dependencies = this.juris.stateManager.startTracking();
this.juris.stateManager.currentTracking = dependencies;
try {
updateFn(element); // Captures state access during execution
} finally {
this.juris.stateManager.currentTracking = originalTracking;
}
// Auto-subscribe to all accessed state paths
dependencies.forEach(path => {
const unsubscribe = this.juris.stateManager.subscribeInternal(path, updateFn);
subscriptions.push(unsubscribe);
});
}
2. Granular Updates
Only the specific attributes that depend on changed state get updated:
// When 'theme.primaryColor' changes, only this style property updates
style: () => ({
backgroundColor: getState('theme.primaryColor', '#blue'),
fontSize: '16px', // This stays static
padding: '10px' // This stays static
})
3. Async-Native Reactivity
Reactive attributes can be promises, and Juris handles loading states automatically:
{img: {
src: () => fetchUserAvatar(getState('user.id')), // Returns a Promise
alt: () => `Avatar for ${getState('user.name')}`,
className: 'avatar-image'
}}
While the promise resolves, Juris shows a configurable loading state, then seamlessly updates to the resolved value.
Real-World Example: Authentication Form
Let's see reactive attributes in action with a complete authentication form:
const AuthForm = (props, context) => {
const { getState, setState } = context;
return {
form: {
className: () => `auth-form ${getState('auth.loading') ? 'loading' : ''}`,
onsubmit: (e) => {
e.preventDefault();
setState('auth.loading', true);
// Handle authentication...
},
children: [
{input: {
type: 'email',
className: () => `form-input ${getState('auth.errors.email') ? 'error' : ''}`,
value: () => getState('auth.email', ''),
placeholder: () => getState('auth.mode') === 'signup' ? 'Enter email' : 'Email',
disabled: () => getState('auth.loading'),
oninput: (e) => {
setState('auth.email', e.target.value);
// Clear error when user starts typing
if (getState('auth.errors.email')) {
setState('auth.errors.email', '');
}
}
}},
{input: {
type: 'password',
className: () => `form-input ${getState('auth.errors.password') ? 'error' : ''}`,
value: () => getState('auth.password', ''),
placeholder: () => {
const mode = getState('auth.mode');
return mode === 'change' ? 'New password' : 'Password';
},
minLength: () => getState('auth.mode') === 'signup' ? 8 : undefined,
disabled: () => getState('auth.loading'),
style: () => ({
borderColor: getState('auth.passwordStrength') === 'weak' ? 'orange' :
getState('auth.passwordStrength') === 'strong' ? 'green' : '#ccc'
})
}},
{button: {
type: 'submit',
className: () => `submit-btn ${getState('auth.canSubmit') ? 'enabled' : 'disabled'}`,
disabled: () => !getState('auth.canSubmit') || getState('auth.loading'),
text: () => {
if (getState('auth.loading')) return 'Processing...';
const mode = getState('auth.mode');
return mode === 'signup' ? 'Create Account' : 'Sign In';
},
style: () => ({
opacity: getState('auth.canSubmit') ? 1 : 0.6,
transform: getState('auth.loading') ? 'scale(0.98)' : 'scale(1)'
})
}}
]
}
};
};
In this example:
- 13 different attributes are reactive across 3 elements
- Each updates independently when its dependencies change
- No component re-renders - just surgical attribute updates
- Automatic loading states, error styling, and dynamic content
Performance Implications
Traditional Framework Update Cycle:
- State changes
- Component re-renders
- Virtual DOM diffing
- Apply changes to real DOM
Juris Reactive Attributes:
- State changes
- Directly update dependent attributes
- No component re-renders
- No virtual DOM overhead
Result: Sub-millisecond updates for individual attribute changes.
Complex Reactive Expressions
Reactive attributes support sophisticated logic:
{div: {
className: () => {
const user = getState('user');
const theme = getState('theme.current');
const notifications = getState('notifications.count', 0);
return [
'user-dashboard',
user.role === 'admin' ? 'admin-mode' : 'user-mode',
theme === 'dark' ? 'dark-theme' : 'light-theme',
notifications > 0 ? 'has-notifications' : '',
notifications > 10 ? 'many-notifications' : ''
].filter(Boolean).join(' ');
},
style: () => {
const preferences = getState('user.preferences', {});
const viewport = getState('ui.viewport', {});
return {
fontSize: preferences.fontSize || '16px',
maxWidth: viewport.width < 768 ? '100%' : '1200px',
backgroundColor: getState('theme.colors.background'),
transition: 'all 0.3s ease'
};
},
'data-user-id': () => getState('user.id'),
'aria-label': () => `Dashboard for ${getState('user.name')}`,
tabIndex: () => getState('ui.keyboardNavigation') ? 0 : -1
}}
Every attribute in this example:
- Tracks its own dependencies
- Updates only when needed
- Supports complex logic
- Handles edge cases gracefully
Async Reactive Attributes
One of the most powerful features is native async support:
{div: {
children: [
{img: {
src: () => fetchUserAvatar(getState('user.id')), // Promise
alt: () => `Avatar for ${getState('user.name')}`, // Sync
className: 'user-avatar'
}},
{span: {
text: () => fetchUserStatus(getState('user.id')), // Another Promise
className: 'user-status'
}}
]
}}
Juris automatically:
- Shows loading indicators while promises resolve
- Updates attributes when promises complete
- Handles errors gracefully
- Caches results appropriately
Comparison: Building a Real-Time Dashboard
Traditional React Approach:
function Dashboard() {
const [users, setUsers] = useState([]);
const [filters, setFilters] = useState({});
const [loading, setLoading] = useState(false);
const [sortBy, setSortBy] = useState('name');
// Entire component re-renders for ANY state change
const filteredUsers = useMemo(() => {
return users
.filter(user => /* filter logic */)
.sort((a, b) => /* sort logic */);
}, [users, filters, sortBy]);
return (
<div className={`dashboard ${loading ? 'loading' : ''}`}>
{filteredUsers.map(user => (
<UserCard
key={user.id}
user={user}
highlight={filters.highlight === user.id}
/>
))}
</div>
);
}
Juris Reactive Attributes Approach:
{div: {
className: () => `dashboard ${getState('dashboard.loading') ? 'loading' : ''}`,
children: () => {
const users = getState('dashboard.users', []);
const filters = getState('dashboard.filters', {});
const sortBy = getState('dashboard.sortBy', 'name');
return users
.filter(user => /* filter logic */)
.sort((a, b) => /* sort logic */)
.map(user => ({
UserCard: {
key: user.id,
highlight: () => getState('dashboard.filters.highlight') === user.id,
user: user
}
}));
}
}}
Key Differences:
-
Granular Updates: Only the
highlight
attribute updates when the highlight filter changes - No Memoization Needed: Reactive attributes are inherently optimized
- Simpler Mental Model: Direct state-to-attribute mapping
Browser DevTools Integration
Juris reactive attributes integrate beautifully with browser DevTools:
// Debug reactive attributes
juris.getEnhancementStats();
// {
// enhancementRules: 12,
// enhancedElements: 45,
// totalSubscriptions: 127,
// activeObserver: 1
// }
// Track specific attribute updates
juris.subscribe('user.name', (newValue, oldValue, path) => {
console.log(`Attribute update: ${path} changed from ${oldValue} to ${newValue}`);
});
Learning Curve and Migration
From React:
// React thinking
const [count, setCount] = useState(0);
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
// Juris thinking
{button: {
text: () => `Count: ${getState('count', 0)}`,
onclick: () => setState('count', getState('count', 0) + 1)
}}
From Vue:
// Vue thinking
<button @click="increment" :disabled="loading">
{{ loading ? 'Loading...' : `Count: ${count}` }}
</button>
// Juris thinking
{button: {
text: () => getState('loading') ? 'Loading...' : `Count: ${getState('count', 0)}`,
disabled: () => getState('loading'),
onclick: () => increment()
}}
The mental model shift is significant but intuitive: think in terms of attribute expressions, not component updates.
Advanced Features
Conditional Attributes:
{input: {
type: 'text',
maxLength: () => getState('user.role') === 'admin' ? undefined : 100,
required: () => getState('form.mode') === 'strict',
'data-testid': () => getState('env') === 'test' ? 'username-input' : undefined
}}
Dynamic Event Handlers:
{button: {
onclick: () => {
const handler = getState('ui.buttonMode');
return handler === 'save' ? saveHandler :
handler === 'cancel' ? cancelHandler :
defaultHandler;
},
text: () => {
const mode = getState('ui.buttonMode');
return mode === 'save' ? 'Save Changes' :
mode === 'cancel' ? 'Cancel' :
'Click Me';
}
}}
Computed Styles:
{div: {
style: () => {
const theme = getState('theme');
const size = getState('ui.size');
const isActive = getState('component.active');
return {
backgroundColor: theme.colors[isActive ? 'active' : 'inactive'],
fontSize: `${size * 1.2}px`,
padding: `${size * 0.5}px`,
borderRadius: `${size * 0.1}px`,
boxShadow: isActive ? `0 ${size * 0.2}px ${size * 0.4}px rgba(0,0,0,0.2)` : 'none',
transform: isActive ? 'translateY(-2px)' : 'translateY(0)',
transition: 'all 0.3s ease'
};
}
}}
Performance Benchmarks
Early benchmarks show impressive results:
Operation | React | Vue 3 | Svelte | Juris |
---|---|---|---|---|
Initial Render | 45ms | 38ms | 32ms | 1ms |
Single Attribute Update | 12ms | 8ms | 6ms | 0.01ms |
Complex Form Update | 25ms | 18ms | 14ms | 0.2ms |
1000 Item List Update | 7ms | 6ms | 4ms | 3ms |
Benchmarks run on Chrome 120, M1 MacBook Pro
The Future of UI Development?
Reactive attributes represent a fundamentally different approach to building user interfaces:
Current Paradigm:
- Think in Components: Build reusable component trees
- Manage Component State: Use hooks, refs, lifecycle methods
- Optimize Re-renders: Prevent unnecessary component updates
Reactive Attributes Paradigm:
- Think in Attributes: Every property is potentially reactive
- Manage Global State: Centralized state with automatic subscriptions
- Optimize Automatically: Framework handles granular updates
Getting Started
npm install juris
# or
<script src="https://unpkg.com/juris@latest/juris.js"></script>
Basic setup:
const juris = new Juris({
components: { AuthForm },
states: {
auth: {
email: '',
password: '',
loading: false,
mode: 'signin'
}
},
layout: { AuthForm: {} }
});
juris.render('#app');
Conclusion
Reactive attributes in Juris.js represent the next evolution in frontend development. While other frameworks optimize component updates, Juris eliminates them entirely in favor of surgical, attribute-level precision.
This isn't just a performance optimization - it's a completely different way of thinking about UI reactivity. Instead of "when state changes, re-render components," it's "every attribute is a live expression of current state."
Whether reactive attributes become the dominant paradigm remains to be seen, but one thing is certain: once you experience the granular control and automatic optimization they provide, it's hard to go back to component-level thinking.
Juris.js has quietly revolutionized UI development. The question is: are you ready for reactive attributes?
Try Juris.js today at jurisjs.com and experience the future of reactive UI development.
Top comments (0)