Duct UI

Duct vs Other Frameworks

How Duct compares to React, Vue, and Svelte

Understanding how Duct differs from popular frameworks helps you make informed architectural decisions. Here's an honest comparison of approaches and trade-offs.

Framework Comparison Table

FeatureDuctReactVueSvelteWeb Components
ArchitectureBlueprint pattern with separationComponent with mixed concernsSFC with sectionsCompiled componentsCustom elements with class
Virtual DOM❌ No✅ Yes✅ Yes❌ No❌ No
State ManagementExternal/ManualuseState/useReducerReactive dataReactive storesManual/Properties
Learning CurveLowMedium-HighMediumLow-MediumMedium
Template SyntaxJSXJSXTemplate/JSXEnhanced HTMLHTML/Template literals
Logic LocationSeparate bind functionMixed in componentScript sectionScript sectionClass methods
ReactivityEvent-drivenRe-render basedProxy-basedCompile-timeManual/Observers

Detailed Comparisons

Notice
Pay attention to how Duct code is direct, explicit, and easily understandable. What you see is what executes - no hidden abstractions, magic behaviors, or complex state management. This makes debugging straightforward and code behavior predictable.

Duct vs React

React popularized component-based UI development, but Duct takes a fundamentally different approach to organizing component logic.

React Component

function Button({ label, onClick }) {
  const [clicked, setClicked] = useState(false);

  return (
    <button
      className={clicked ? 'btn-clicked' : 'btn'}
      onClick={() => {
        setClicked(true);
        onClick();
      }}
    >
      {label}
    </button>
  );
}

Duct Component

// Render function
function render(props) {
  return (
    <button class="btn" data-clicked="false">
      {props.label}
    </button>
  );
}

// Bind function
function bind(el, eventEmitter) {
  let clicked = false;

  const handleClick = () => {
    clicked = true;
    el.dataset.clicked = 'true';
    el.className = 'btn-clicked';
    eventEmitter.emit('click');
  };

  el.addEventListener('click', handleClick);

  return { release: () => el.removeEventListener('click', handleClick) };
}

Duct Advantages

  • ✓ No re-rendering overhead
  • ✓ Direct DOM control
  • ✓ Clear separation of concerns
  • ✓ Explicit behavior
  • ✓ No hooks complexity

React Advantages

  • • Large ecosystem
  • • Extensive tooling
  • • Huge community
  • • More job opportunities
  • • Mature libraries

Duct vs Vue

Vue offers a middle ground between React and traditional templating, while Duct goes further in separating concerns.

Vue Component

<template>
  <button
    :class="buttonClass"
    @click="handleClick"
  >
    {{ label }}
  </button>
</template>

<script>
export default {
  props: ['label'],
  data() {
    return { clicked: false };
  },
  computed: {
    buttonClass() {
      return this.clicked ? 'btn-clicked' : 'btn';
    }
  },
  methods: {
    handleClick() {
      this.clicked = true;
      this.$emit('click');
    }
  }
};
</script>

Duct Component

// Template is pure presentation
function render(props) {
  return (
    <button class="btn">
      {props.label}
    </button>
  );
}

// Logic is completely separate
function bind(el, eventEmitter) {
  let clicked = false;

  function handleClick() {
    clicked = true;
    el.className = 'btn-clicked';
    eventEmitter.emit('click');
  }

  el.addEventListener('click', handleClick);

  return {
    release: () => el.removeEventListener('click', handleClick)
  };
}

Duct Advantages

  • ✓ No reactivity magic to debug
  • ✓ Explicit DOM control
  • ✓ Clear separation of template/logic
  • ✓ Standard JavaScript patterns
  • ✓ Predictable performance

Vue Advantages

  • • Gentle learning curve
  • • Progressive enhancement
  • • Excellent documentation
  • • Built-in state management
  • • Single-file components

Duct vs Svelte

Both Duct and Svelte avoid virtual DOM, but they differ significantly in their compilation and component organization approaches.

Svelte Component

<script>
  export let label;
  let clicked = false;

  function handleClick() {
    clicked = true;
    dispatch('click');
  }
</script>

<button
  class={clicked ? 'btn-clicked' : 'btn'}
  on:click={handleClick}
>
  {label}
</button>

Duct Component

// Functions are completely separated
function render(props) {
  return <button class="btn">{props.label}</button>;
}

function bind(el, eventEmitter) {
  let clicked = false;

  const handleClick = () => {
    clicked = true;
    el.className = 'btn-clicked';
    eventEmitter.emit('click');
  };

  el.addEventListener('click', handleClick);

  return { release: () => el.removeEventListener('click', handleClick) };
}

// Explicit blueprint creation
const Button = createBlueprint(
  { id: "my/button" },
  render,
  { bind }
);

export default Button;

Duct Advantages

  • ✓ No compile step required
  • ✓ Runtime transparency
  • ✓ Standard build tools
  • ✓ Explicit behavior
  • ✓ Easy debugging

Svelte Advantages

  • • Tiny bundle sizes
  • • Compile-time optimizations
  • • Built-in animations
  • • Simple reactive syntax
  • • No runtime overhead

Duct vs Web Components

Both Duct and Web Components embrace direct DOM manipulation and standards-based approaches, but they differ in their component organization and abstraction levels.

Web Components

class ButtonElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
        button { padding: 8px 16px; }
        .clicked { background: blue; }
      </style>
      <button>${this.getAttribute('label')}</button>
    `;

    this.button = this.shadowRoot.querySelector('button');
    this.button.addEventListener('click', this.handleClick.bind(this));
  }

  handleClick() {
    this.button.classList.add('clicked');
    this.dispatchEvent(new CustomEvent('button-click'));
  }

  disconnectedCallback() {
    this.button?.removeEventListener('click', this.handleClick);
  }
}

customElements.define('my-button', ButtonElement);

Duct Component

// Render function
function render(props) {
  return (
    <button class="btn">
      {props.label}
    </button>
  );
}

// Bind function
function bind(el, eventEmitter) {
  let clicked = false;

  const handleClick = () => {
    clicked = true;
    el.classList.add('clicked');
    eventEmitter.emit('click');
  };

  el.addEventListener('click', handleClick);

  return {
    release: () => el.removeEventListener('click', handleClick)
  };
}

// Create component
const Button = createBlueprint(
  { id: "my/button" },
  render,
  { bind }
);

export default Button;

Duct Advantages

  • ✓ Clear separation of render/logic concerns
  • ✓ No Shadow DOM complexity
  • ✓ Simpler component composition
  • ✓ Framework-agnostic JSX templates
  • ✓ Explicit lifecycle management
  • ✓ Better TypeScript integration

Web Components Advantages

  • • Native browser standard
  • • True encapsulation with Shadow DOM
  • • Framework interoperability
  • • No build step required
  • • CSS isolation by default
  • • Custom element lifecycle

When to Choose Each Framework

Choose Duct When

  • ✓ You value explicit, debuggable code
  • ✓ You need direct DOM control
  • ✓ You want minimal abstractions
  • ✓ You're building long-lived applications
  • ✓ You work with AI code generation
  • ✓ You prefer separation of concerns
  • ✓ You need predictable performance
  • ✓ You want clear debugging paths
  • ✓ You prioritize maintainability

Choose Vue When

  • • You want balanced complexity
  • • You like single-file components
  • • You need official router/state management
  • • You're migrating from jQuery
  • • You want good documentation

Choose React When

  • • You need the largest ecosystem
  • • You have React expertise
  • • You need enterprise support
  • • You're building complex SPAs
  • • You need extensive third-party libraries

Choose Svelte When

  • • You want the smallest bundle sizes
  • • You like compile-time optimizations
  • • You want built-in animations
  • • You prefer simpler mental models
  • • You're building content sites

Choose Web Components When

  • • You need true framework interoperability
  • • You want standards-based components
  • • You need CSS encapsulation
  • • You're building design systems
  • • You want no build dependencies
Remember: Each framework solves different problems well. Choose based on your project needs, team expertise, and long-term maintenance goals rather than popularity alone.