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: nonerather than unmounting, so a permission change can show or hide the element without remounting it and losing its state. - It re-evaluates in the
updatedhook, 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.
Related
usePermissions— the same check, in script.useNavPermission— the menu's own gate, which treats guests differently.- Auth and permissions — the rules and the guard.
- Permissions — setting them up.