Files
stack/packages/mosaic/framework/skills/vue-best-practices/reference/render-function-custom-directives.md
T
fargo 1a822493ba format: apply repo prettier (3.8.1) to the folded skills tree
963 markdown files reformatted with the repository's pinned prettier so
pnpm format:check covers the folded tree like every other repo file.

The formatter's embedded-language pass also normalized code fences
(TS semicolons, closed HTML tags in examples, lowercased CSS hex colors,
one renumbered list that skipped an index). Alphanumeric token deltas vs
the fold commit were audited file-by-file; all are formatter-equivalent
markup normalizations plus the four sanitized skills.
2026-08-19 14:37:17 -05:00

4.3 KiB

title, impact, impactDescription, type, tags
title impact impactDescription type tags
Use withDirectives for Custom Directives in Render Functions LOW Custom directives require special handling with withDirectives helper best-practice
vue3
render-function
directives
custom-directive

Use withDirectives for Custom Directives in Render Functions

Impact: LOW - To apply custom directives to vnodes in render functions, use the withDirectives helper. Attempting to apply directives through props or other means will not work.

The withDirectives function wraps a vnode and applies directives with their values, arguments, and modifiers.

Task Checklist

  • Import withDirectives from 'vue' when using custom directives
  • Import or define the directive object
  • Pass directive as array: [directive, value, argument, modifiers]
  • Multiple directives can be applied at once

Incorrect:

import { h } from 'vue';

const vFocus = { mounted: (el) => el.focus() };

export default {
  setup() {
    return () => {
      // WRONG: Directives don't work as props
      return h('input', {
        'v-focus': true, // This doesn't work
      });
    };
  },
};

Correct:

import { h, withDirectives } from 'vue';

// Custom directive
const vFocus = {
  mounted: (el) => el.focus(),
};

export default {
  setup() {
    return () => {
      // CORRECT: Use withDirectives
      return withDirectives(
        h('input'),
        [[vFocus]], // Array of directive tuples
      );
    };
  },
};

Directive with Value, Argument, and Modifiers

The directive tuple format: [directive, value, argument, modifiers]

import { h, withDirectives, resolveDirective } from 'vue';

// Directive definition
// Usage in template: <div v-pin:top.animate="200">
const vPin = {
  mounted(el, binding) {
    console.log(binding.value); // 200
    console.log(binding.arg); // 'top'
    console.log(binding.modifiers); // { animate: true }

    el.style.position = 'fixed';
    el.style[binding.arg] = binding.value + 'px';
  },
};

export default {
  setup() {
    return () =>
      withDirectives(h('div', 'Pinned content'), [
        // [directive, value, argument, modifiers]
        [vPin, 200, 'top', { animate: true }],
      ]);
  },
};

Multiple Directives

import { h, withDirectives } from 'vue';

const vFocus = { mounted: (el) => el.focus() };
const vTooltip = {
  mounted(el, { value }) {
    el.title = value;
  },
};

export default {
  setup() {
    return () =>
      withDirectives(h('input', { placeholder: 'Enter text' }), [
        [vFocus],
        [vTooltip, 'This is a tooltip'],
      ]);
  },
};

Using Registered Directives

For globally or locally registered directives, use resolveDirective:

import { h, withDirectives, resolveDirective } from 'vue';

// Assuming v-focus and v-tooltip are registered globally
export default {
  setup() {
    return () => {
      const focus = resolveDirective('focus');
      const tooltip = resolveDirective('tooltip');

      return withDirectives(h('input'), [[focus], [tooltip, 'Helpful tip']]);
    };
  },
};

Practical Example: Click Outside Directive

import { h, withDirectives, ref } from 'vue';

const vClickOutside = {
  mounted(el, binding) {
    el._clickOutside = (event) => {
      if (!el.contains(event.target)) {
        binding.value(event);
      }
    };
    document.addEventListener('click', el._clickOutside);
  },
  unmounted(el) {
    document.removeEventListener('click', el._clickOutside);
  },
};

export default {
  setup() {
    const isOpen = ref(true);
    const closeDropdown = () => {
      isOpen.value = false;
    };

    return () =>
      isOpen.value
        ? withDirectives(h('div', { class: 'dropdown' }, 'Dropdown content'), [
            [vClickOutside, closeDropdown],
          ])
        : null;
  },
};

JSX with Directives

Directives in JSX also require withDirectives:

import { withDirectives } from 'vue';

const vFocus = { mounted: (el) => el.focus() };

export default {
  setup() {
    return () => withDirectives(<input placeholder="Search..." />, [[vFocus]]);
  },
};

Reference