A Medium post exploring the psychology behind JS developers' reluctance to write HTML, and how modern frameworks like Juris bridge the gap
The Uncomfortable Truth
Walk into any modern web development team and ask a JavaScript developer to write some HTML. Watch their face. You'll see a subtle wince, followed by rapid-fire suggestions: "Why don't we use React components?" or "Let me create a JSX template for that."
It's not that they can't write HTML—they absolutely can. So why the aversion? Why do developers who can architect complex state management systems and async data flows suddenly feel uncomfortable writing <div>
tags?
The Root of the Fear: Loss of Programmatic Control
The answer lies in what I call "programmatic comfort zone syndrome." JavaScript developers are accustomed to having programmatic control over everything:
// This feels natural to a JS developer
const users = await fetchUsers();
const userList = users.map(user =>
createUserElement(user.name, user.email)
);
container.appendChild(userList);
But this feels alien:
<!-- This feels "static" and "powerless" -->
<div class="user-list">
<div class="user">John Doe</div>
<div class="user">Jane Smith</div>
</div>
The HTML lacks the dynamic expressiveness that JavaScript provides. There's no .map()
, no conditionals, no variables, no functions. It feels like stepping back into the stone age of programming.
The Framework Revolution: Bringing Programming to Markup
This discomfort led to the framework revolution. React, Vue, Angular—they all solve the same fundamental problem: making markup feel like programming.
JSX: HTML That Thinks It's JavaScript
// JSX feels "right" to JS developers
function UserList({ users }) {
return (
<div className="user-list">
{users.map(user => (
<div key={user.id} className="user">
{user.name} - {user.email}
</div>
))}
</div>
);
}
Suddenly, markup has:
- Variables (
{user.name}
) - Loops (
users.map()
) - Conditions (
{isActive && <span>Active</span>}
) - Functions (the component itself)
The Async Problem: Where Even JSX Falls Short
But even JSX has limitations. What happens when your data is asynchronous? Traditional approaches get messy fast:
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchUser(userId)
.then(setUser)
.catch(setError)
.finally(() => setLoading(false));
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return <div>User not found</div>;
return (
<div className="user-profile">
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
Look at all that boilerplate! Three state variables, a useEffect hook, three conditional returns—just to handle one async operation. This is where even modern frameworks start to feel clunky.
Enter Native Async Support: The Juris Approach
What if markup could handle async operations natively? What if you could write this instead:
// Juris: Native async support in VDOM
const UserProfile = (props, context) => ({
div: {
className: 'user-profile',
children: [
{h1: {text: () => fetchUser(props.userId).then(u => u.name)}},
{p: {text: () => fetchUser(props.userId).then(u => u.email)}}
]
}
});
No useState. No useEffect. No loading states. No error handling boilerplate. The framework handles all of that automatically.
This is what Juris accomplishes with its objectToHtml()
method. It provides native async support at the VDOM level, where:
-
Functions return promises:
text: () => Promise.resolve('Hello')
- Automatic loading states: Framework shows placeholders while promises resolve
- Error boundaries: Failed promises get error UI automatically
- Caching: Resolved promises are cached for performance
- Reactivity: State changes trigger re-evaluation of async functions
The Psychology Behind the Fear
The real reason JS developers avoid HTML isn't technical—it's psychological:
1. Perceived Loss of Power
HTML feels static. JavaScript feels dynamic. Moving from JS to HTML feels like trading a Ferrari for a bicycle.
2. Mental Model Mismatch
JS developers think in terms of:
- Data transformation
- State management
- Function composition
- Async operations
HTML traditionally offers none of these patterns.
3. Tooling Expectations
JS developers expect:
- IntelliSense and autocomplete
- Type checking
- Refactoring support
- Hot reloading
Traditional HTML development lacks these developer experience features.
4. Component Thinking
Modern developers think in reusable components, not static markup. HTML templates feel like a step backward from component-based architecture.
The Bridge: VDOM as Programmatic HTML
The solution isn't to abandon HTML or force developers back to templates. It's to create better abstractions that provide HTML's semantic benefits with JavaScript's programmatic power.
Juris's approach is particularly elegant:
// Looks like JavaScript objects (familiar)
// Compiles to HTML (semantic)
// Supports async natively (powerful)
// Maintains reactivity (dynamic)
const App = (props, context) => ({
article: {
children: [
{header: {
children: [
{h1: {text: () => context.getState('post.title')}},
{time: {
datetime: () => context.getState('post.publishedAt'),
text: () => formatDate(context.getState('post.publishedAt'))
}}
]
}},
{main: {
innerHTML: () => context.getState('post.content')
}}
]
}
});
This approach:
- Feels like JavaScript (objects and functions)
- Compiles to semantic HTML (article, header, main, time)
- Supports reactivity (getState calls)
- Handles async natively (promises in functions)
- Maintains component patterns (reusable functions)
The Future: Embracing Hybrid Approaches
The fear of HTML is really a fear of limitations. Modern frameworks like Juris show us that we don't have to choose between semantic markup and programmatic power.
The future lies in approaches that:
- Preserve HTML semantics for accessibility and SEO
- Provide JavaScript expressiveness for dynamic behavior
- Handle async operations natively without boilerplate
- Support component patterns for reusability
- Offer excellent developer experience with tooling and debugging
Conclusion: It's Not Fear, It's Evolution
JavaScript developers aren't afraid of HTML—they've simply evolved beyond its traditional limitations. They've tasted the power of programmatic markup and don't want to go back.
The challenge for framework authors is to provide that power while preserving HTML's strengths: semantics, accessibility, and progressive enhancement.
Frameworks like Juris point toward a future where developers can have their cake and eat it too: the expressiveness of JavaScript with the semantic richness of HTML, all with native async support that makes complex applications feel simple again.
The "fear" of HTML is really an appetite for better tools. And that appetite is driving innovation that benefits everyone—from developers who want expressive markup to users who need fast, accessible applications.
Want to try Juris's native async VDOM approach? Check out the framework on GitHub and see how 2,500 lines of code can eliminate the complexity that makes developers avoid HTML.
Top comments (0)