npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@fastkit/vue-disabled-reason

v0.2.0

Published

A headless Vue utility that monitors disabled elements and provides a slot or render function to display the reason.

Readme

@fastkit/vue-disabled-reason

🌐 English | 日本語

A library providing utility functions to define headless Vue.js components for monitoring disabled elements and displaying reasons.

Features

  • Headless Design: Create components that handle only logic without providing UI
  • Automatic Disabled State Detection: Automatically monitors disabled and aria-disabled attributes
  • Flexible Display Control: Display reasons through slots or render functions
  • Full TypeScript Support: Type safety with strict type definitions
  • Advanced Customization: Support for custom VNode traversal handling
  • Performance Optimization: Efficient element monitoring and rendering control

Installation

npm install @fastkit/vue-disabled-reason
# or
pnpm add @fastkit/vue-disabled-reason

Required CSS Import

Before using the library, make sure to import the CSS file. This CSS sets position: relative on monitored elements, allowing the library to properly insert reason display elements.

// Import at the main entry point first
import '@fastkit/vue-disabled-reason/vue-disabled-reason.css';

Or import in HTML:

<link rel="stylesheet" href="@fastkit/vue-disabled-reason/vue-disabled-reason.css">

Basic Usage

Simple Disabled Reason Display

<template>
  <div>
    <!-- Wrap disabled button -->
    <MyDisabledReason>
      <button :disabled="hasNoPermission">
        Execute Delete
      </button>

      <!-- Display disabled reason via slot -->
      <template #reason>
        Insufficient permissions
      </template>
    </MyDisabledReason>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { MyDisabledReason } from './components/MyDisabledReason.vue';

const hasNoPermission = ref(true);
</script>

Reason Display with reason Property

You can also use the reason property instead of slots.

<template>
  <div>
    <!-- Specify reason with reason property -->
    <MyDisabledReason reason="Administrator privileges required">
      <button :disabled="!isAdmin">
        System Settings
      </button>
    </MyDisabledReason>

    <!-- Dynamic reason setting -->
    <MyDisabledReason :reason="disableReason">
      <button :disabled="isDisabled">
        Delete Data
      </button>
    </MyDisabledReason>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue';

const isAdmin = ref(false);
const hasDeletePermission = ref(false);

const disableReason = computed(() => 
  hasDeletePermission.value ? null : 'Delete permission required'
);
</script>

Custom Component Definition

import { defineDisabledReasonComponent } from '@fastkit/vue-disabled-reason';
import { VTooltip } from './components/VTooltip.vue';

export const MyDisabledReason = defineDisabledReasonComponent({
  name: 'MyDisabledReason',
  setup(api) {
    return (reason) => (
      <VTooltip
        disabled={!reason || !api.disabled}
        v-slots={{
          activator: ({ attrs }) => <span {...attrs} />,
          default: () => reason,
        }}
      />
    );
  },
});

Advanced Usage

Custom Container Specification

By default, reason display elements are inserted into the root element of the monitored target. You can customize the insertion position using DISABLED_REASON_CONTAINER_BIND.

<template>
  <MyDisabledReason>
    <div class="button-container">
      <!-- Specify insertion position with DISABLED_REASON_CONTAINER_BIND -->
      <div v-bind="DISABLED_REASON_CONTAINER_BIND">
        <button :disabled="isDisabled">Change Settings</button>
      </div>
      <span>System Settings</span>
    </div>
    
    <template #reason>
      No permission to change system settings
    </template>
  </MyDisabledReason>
</template>

<script setup lang="ts">
import { DISABLED_REASON_CONTAINER_BIND } from '@fastkit/vue-disabled-reason';
const isDisabled = ref(true);
</script>

Individual Handling for Multiple Buttons

When there are multiple disableable elements, each must be wrapped with individual components.

<template>
  <div>
    <h3>Data Management</h3>
    <div>
      <!-- Edit button -->
      <MyDisabledReason>
        <div v-bind="DISABLED_REASON_CONTAINER_BIND">
          <button :disabled="!canEdit">Edit</button>
        </div>
        <template #reason>
          No edit permission
        </template>
      </MyDisabledReason>

      <!-- Delete button -->
      <MyDisabledReason>
        <div v-bind="DISABLED_REASON_CONTAINER_BIND">
          <button :disabled="!canDelete">Delete</button>
        </div>
        <template #reason>
          No delete permission
        </template>
      </MyDisabledReason>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import { DISABLED_REASON_CONTAINER_BIND } from '@fastkit/vue-disabled-reason';

const userRole = ref('viewer'); // 'admin', 'editor', 'viewer'
const canEdit = computed(() => ['admin', 'editor'].includes(userRole.value));
const canDelete = computed(() => userRole.value === 'admin');
</script>

API Reference

defineDisabledReasonComponent

Defines a headless disabled reason component.

function defineDisabledReasonComponent<Props>(
  options: DisabledReasonComponentOptions<Props>
): DefineComponent

Options:

  • name?: Component name
  • props?: Additional property definitions
  • skipVNode?: VNode traversal skip handler
  • setup: Setup function that returns a render function

DisabledReasonAPI

API available within components.

interface DisabledReasonAPI<Props = {}> {
  readonly props: ExtractPropTypes<Props>;
  readonly disabled: boolean;
}

DISABLED_REASON_CONTAINER_BIND

Bind object for customizing the insertion position of disabled reason display elements.

const DISABLED_REASON_CONTAINER_BIND: {
  'data-disabled-reason-container': '';
}

Usage:

<template>
  <MyDisabledReason>
    <!-- Disabled reason will be inserted within this element -->
    <div v-bind="DISABLED_REASON_CONTAINER_BIND">
      <button :disabled="isDisabled">Button</button>
    </div>
    
    <template #reason>
      Reason text for being disabled
    </template>
  </MyDisabledReason>
</template>

Notes:

  • Only the first disableable element found within the specified container will be monitored
  • When there are multiple disableable elements, wrap each with individual components

Monitored Elements

Standard Disableable Elements

  • <button>
  • <input> (all types)
  • <textarea>
  • <select>
  • <fieldset>
  • <optgroup>
  • <option>

ARIA-Compatible Elements

Elements with the following roles that have aria-disabled attribute (including <a> tags):

  • button
  • link
  • menuitem
  • menuitemcheckbox
  • menuitemradio
  • option
  • radio
  • slider
  • spinbutton
  • switch
  • tab
  • checkbox
  • gridcell
  • textbox

Performance Considerations

  • Efficient Element Search: Minimal DOM traversal
  • Rendering Optimization: Re-render only when disabled state changes
  • Memory Management: Proper cleanup processing
  • Event Optimization: Register event listeners only when necessary

Limitations

  • Single Element Monitoring: One component instance monitors only the first disableable element found. For multiple elements, wrap each with individual components
  • Dynamic Elements: Monitoring dynamically added elements requires remounting
  • CSS Pseudo-classes: Cannot detect disabled states set by CSS pseudo-classes (:disabled, etc.)
  • JavaScript Dynamic Changes: Dynamic disabled state changes via JavaScript are reflected after nextTick

Related Packages

  • @fastkit/vue-utils - Vue.js utilities (dependency)
  • vue - Vue.js framework (peer dependency)

License

MIT