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

potentio

v0.0.2

Published

Unstyled & accessible knob primitive for React and Vue

Readme

Potentio

Unstyled & accessible knob primitive for React and Vue.

npm version License: MIT

Based on the excellent react-knob-headless by satelllte.

Features

  • 🎨 Headless - no styles included, fully customizable
  • 🎯 Framework support - React and Vue 3 components
  • ♿ Accessible - follows ARIA slider pattern
  • 🎹 Keyboard controls - arrow keys, page up/down, home/end
  • 🖱️ Pointer controls - mouse and touch support
  • 📐 Flexible - linear or non-linear value mapping
  • 🔄 Shared logic - React and Vue components share the same core logic

Installation

npm install potentio

Running the Demo

To see both React and Vue implementations in action:

# From the root directory
npm run demo

# Or manually:
cd demo
npm install
npm run dev

Then open http://localhost:5173 in your browser.

Usage

React

import React, { useState } from "react";
import {
  ReactKnobHeadless,
  ReactKnobHeadlessLabel,
  ReactKnobHeadlessOutput,
  useReactKnobKeyboardControls,
} from "potentio";

function App() {
  const [value, setValue] = useState(50);

  const { onKeyDown } = useReactKnobKeyboardControls({
    valueRaw: value,
    valueMin: 0,
    valueMax: 100,
    step: 1,
    stepLarger: 10,
    onValueRawChange: setValue,
  });

  return (
    <ReactKnobHeadless
      aria-label="Volume"
      valueRaw={value}
      valueMin={0}
      valueMax={100}
      dragSensitivity={0.006}
      valueRawRoundFn={Math.round}
      valueRawDisplayFn={(v) => `${v}%`}
      onValueRawChange={setValue}
      onKeyDown={onKeyDown}
    >
      {/* Your custom knob UI */}
      <div
        className="knob-indicator"
        style={{
          transform: `rotate(${(value / 100) * 270 - 135}deg)`,
        }}
      />
    </ReactKnobHeadless>
  );
}

Vue

<template>
  <VueKnobHeadless
    aria-label="Volume"
    :value-raw="value"
    :value-min="0"
    :value-max="100"
    :drag-sensitivity="0.006"
    :value-raw-round-fn="Math.round"
    :value-raw-display-fn="(v) => `${v}%`"
    :on-value-raw-change="setValue"
    :wheel-sensitivity="0.01"
    @keydown="onKeyDown"
  >
    <!-- Your custom knob UI -->
    <div
      class="knob-indicator"
      :style="{
        transform: `rotate(${(value / 100) * 270 - 135}deg)`,
      }"
    />
  </VueKnobHeadless>
</template>

<script setup>
import { ref } from "vue";
import {
  VueKnobHeadless,
  VueKnobHeadlessLabel,
  VueKnobHeadlessOutput,
  useVueKnobKeyboardControls,
} from "potentio";

const value = ref(50);

const setValue = (newValue) => {
  value.value = newValue;
};

const { onKeyDown } = useVueKnobKeyboardControls({
  valueRaw: value.value,
  valueMin: 0,
  valueMax: 100,
  step: 1,
  stepLarger: 10,
  onValueRawChange: setValue,
});
</script>

API

Props

| Prop | Type | Description | Required | | --------------------- | ------------------------------------------------- | ------------------------------------- | -------- | | valueRaw | number | Current raw value | ✓ | | valueMin | number | Minimum value | ✓ | | valueMax | number | Maximum value | ✓ | | dragSensitivity | number | Sensitivity of drag gesture | ✓ | | valueRawRoundFn | (value: number) => number | Function to round the raw value | ✓ | | valueRawDisplayFn | (value: number) => string | Function to format display text | ✓ | | onValueRawChange | (value: number) => void | Callback when value changes | ✓ | | axis | 'x' \| 'y' \| 'xy' | Drag axis (default: 'y') | | | orientation | 'horizontal' \| 'vertical' | DEPRECATED: Use axis instead | | | includeIntoTabOrder | boolean | Include in tab order (default: false) | | | mapTo01 | (x: number, min: number, max: number) => number | Map value to 0-1 range | | | mapFrom01 | (x: number, min: number, max: number) => number | Map from 0-1 range to value | | | disabled | boolean | Disable all interactions | |

Controls

Mouse/Touch

  • Drag: Click and drag to adjust value
  • Mouse Wheel: Scroll to adjust value (Vue only)

Keyboard

  • Arrow Up/Right: Increase by step
  • Arrow Down/Left: Decrease by step
  • Page Up: Increase by stepLarger
  • Page Down: Decrease by stepLarger
  • Home: Set to minimum
  • End: Set to maximum

Vue-specific Props

| Prop | Type | Description | Default | | ------------------ | -------- | -------------------------- | ------- | | wheelSensitivity | number | Sensitivity of mouse wheel | 0.01 |

Complete Example

React with Label and Output

import React, { useState } from "react";
import {
  ReactKnobHeadless,
  ReactKnobHeadlessLabel,
  ReactKnobHeadlessOutput,
  useReactKnobKeyboardControls,
} from "potentio";

function VolumeControl() {
  const [volume, setVolume] = useState(50);
  const knobId = "volume-knob";
  const labelId = "volume-label";

  const { onKeyDown } = useReactKnobKeyboardControls({
    valueRaw: volume,
    valueMin: 0,
    valueMax: 100,
    step: 1,
    stepLarger: 10,
    onValueRawChange: setVolume,
  });

  return (
    <div className="volume-control">
      <ReactKnobHeadlessLabel id={labelId}>Volume</ReactKnobHeadlessLabel>

      <ReactKnobHeadless
        id={knobId}
        aria-labelledby={labelId}
        valueRaw={volume}
        valueMin={0}
        valueMax={100}
        dragSensitivity={0.006}
        valueRawRoundFn={Math.round}
        valueRawDisplayFn={(v) => `${v}%`}
        onValueRawChange={setVolume}
        onKeyDown={onKeyDown}
        className="knob"
      >
        <div className="knob-track" />
        <div className="knob-thumb" />
      </ReactKnobHeadless>

      <ReactKnobHeadlessOutput htmlFor={knobId}>
        {volume}%
      </ReactKnobHeadlessOutput>
    </div>
  );
}

Vue with Label and Output

<template>
  <div class="volume-control">
    <VueKnobHeadlessLabel :id="labelId"> Volume </VueKnobHeadlessLabel>

    <VueKnobHeadless
      :id="knobId"
      :aria-labelledby="labelId"
      :value-raw="volume"
      :value-min="0"
      :value-max="100"
      :drag-sensitivity="0.006"
      :value-raw-round-fn="Math.round"
      :value-raw-display-fn="(v) => `${v}%`"
      :on-value-raw-change="setVolume"
      :wheel-sensitivity="0.004"
      @keydown="onKeyDown"
      class="knob"
    >
      <div class="knob-track" />
      <div class="knob-thumb" />
    </VueKnobHeadless>

    <VueKnobHeadlessOutput :html-for="knobId">
      {{ volume }}%
    </VueKnobHeadlessOutput>
  </div>
</template>

<script setup>
import { ref } from "vue";
import {
  VueKnobHeadless,
  VueKnobHeadlessLabel,
  VueKnobHeadlessOutput,
  useVueKnobKeyboardControls,
} from "potentio";

const volume = ref(50);
const knobId = "volume-knob";
const labelId = "volume-label";

const setVolume = (newValue) => {
  volume.value = newValue;
};

const { onKeyDown } = useVueKnobKeyboardControls({
  valueRaw: volume.value,
  valueMin: 0,
  valueMax: 100,
  step: 1,
  stepLarger: 10,
  onValueRawChange: setVolume,
});
</script>

Available Exports

// React components
import {
  ReactKnobHeadless, // Main knob component
  ReactKnobHeadlessLabel, // Accessible label component
  ReactKnobHeadlessOutput, // Output display component
  useReactKnobKeyboardControls, // Keyboard controls hook
} from "potentio";

// Vue components
import {
  VueKnobHeadless, // Main knob component
  VueKnobHeadlessLabel, // Accessible label component
  VueKnobHeadlessOutput, // Output display component
  useVueKnobKeyboardControls, // Keyboard controls composable
} from "potentio";

// Shared utilities (if needed for custom implementations)
import {
  calculateDragValue, // Core drag calculation
  calculateKeyboardValue, // Keyboard value calculation
  getDragAxis, // Axis utility
  getAriaOrientation, // ARIA orientation utility
} from "potentio";

Known Issues

  • Mouse wheel support is only available in the Vue version
  • Horizontal axis (axis: 'x') and XY axis (axis: 'xy') may have issues in the Vue version. PRs welcome!

License

MIT