Skip to content

v-permission ​

Hides an element when the user cannot do that thing. One string, in CASL's "action subject" grammar.

Registration ​

Registered globally in a project built from the template:

ts
app.directive('permission', vPermission)

To use it in one component instead:

ts
import { vPermission } from '@shared/directives/vPermission'

Usage ​

vue
<template>
  <button type="button" v-permission="'create User'">
    Add user
  </button>

  <a v-permission="'delete Invoice'" href="#" @click.prevent="remove()">
    Delete
  </a>
</template>

The value is one string with a single space: the action, then the subject. There is no array form.

Example ​

vue
<script setup lang="ts">
import { usePermissions } from '@shared/composables/usePermissions'

const { can } = usePermissions()

function archive() {}
</script>

<template>
  <div class="flex items-center gap-2">
    <button type="button" v-permission="'update Invoice'" @click="archive()">
      Archive
    </button>

    <p v-if="!can('update', 'Invoice')">
      Ask an administrator to archive this invoice.
    </p>
  </div>
</template>

Notes ​

  • It is defense in depth, not a security boundary. It hides a node in the DOM. The router guard and your backend are the checks that actually hold; anyone can unhide an element in devtools.
  • It sets display: none rather than unmounting, so a permission change can show or hide the element without remounting it and losing its state.
  • It re-evaluates in the updated hook, which Vue calls when the surrounding template re-renders. A CASL rules update re-renders those templates, so the element follows.
  • A binding with no space parses as the action with an empty subject, which no rule matches, so the element hides. A typo fails closed.