Vue.js programmatic rendering example

August 30, 2020

I recently started working on a Vue.js components library and I ran into the following rendering problem.

I wanted a generic button component that dynamically renders the right inner HTML element which is <a> if there is a href props and a <button> otherwise.

<!-- ./Button.vue -->
<template>
  <a
    v-if="href"
    :href="href"
    :disabled="disabled"
    :class="classes"
    @click="onClick"
  >
    <slot />
  </a>
  <button
    v-else
    :type="type"
    :disabled="disabled"
    :class="classes"
    @click="onClick"
  >
    <slot />
  </button>
</template>

<script>
  {
    props: {
      href: String,
      type: String,
      outlined: Boolean,
      disabled: Boolean,
    },
    computed: { ... },
    methods: { ... },
  }
</script>

I firstly come with this template-based solution using both <a> and <button> conditionally displayed using a v-if in the template.

It works, but it doesn’t feel perfect since we need to repeat ourself. Let’s improve the previous code by using a render function :

<!-- ./Button.vue -->
<script>
  {
    props: {
      href: String,
      type: String,
      outlined: Boolean,
      disabled: Boolean,
    },
    methods: { ... },

    render(createElement) {
      const isLink = this.href != null

      return createElement(
        isLink ? 'a' : 'button',
        {
          class: {
            'is-disabled': this.disabled,
            'is-outlined': this.outlined,
          },
          attrs: {
            href: isLink ? this.href : null,
            type: isLink ? null : this.type,
          },
          on: { click: this.onClick }
        },
        this.$slots.default
      )
    }
  }
</script>

Now the inner button element is programmatically created, this removes the repetition in the template for passing attributes, classes, listeners and so on.

Concretely we can replace all the template functionalities like v-if and v-for by a if / else statement or a Array.prototype.map in the render function.

This can be useful in many other situations, like for rendering a list without a root element which is not allowed in the template.

Blog ideas

Upvote what you'd like me to write about next.

  • Nx DTE vs CI job matrix
    Nx Distributed Task Execution (DTE) vs a traditional CI job matrix with manual sharding: a comparison of developer experience, performance, and reliability.
  • Are you ready for a monorepo?
    An opinionated checklist to help you decide whether a monorepo is the right choice for your organization, and how to prepare for the transition.
  • Why monorepos unlock AI coding agents
    Shared context from frontend to backend, consistent conventions, and reliable task graphs: what makes a monorepo the ideal substrate for AI-assisted development.
  • Managing CI flakiness at scale
    Detecting, quarantining, and fixing flaky tests without slowing the pipeline: patterns that survive past 25 engineers.
  • Monitoring CI health
    Tools and techniques for tracking CI performance and reliability over time, and alerting on regressions before they impact developers.
Edouard Bozon

I'm Edouard Bozon, a passionate software engineer based in France, specializing in monorepo architectures, platform engineering, DevOps, and infrastructure. I build tools and workflows that help engineering teams scale their codebases and ship software with confidence.