DEV Community

Pinoy Codie
Pinoy Codie

Posted on

Why JavaScript Developers Are Afraid of Writing HTML: The Search for the Perfect Abstraction

HTML

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);
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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)}}
    ]
  }
});
Enter fullscreen mode Exit fullscreen mode

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')
      }}
    ]
  }
});
Enter fullscreen mode Exit fullscreen mode

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:

  1. Preserve HTML semantics for accessibility and SEO
  2. Provide JavaScript expressiveness for dynamic behavior
  3. Handle async operations natively without boilerplate
  4. Support component patterns for reusability
  5. 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)