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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@monolithrobotics/isoline

v0.5.1

Published

Headless, unstyled Vue 3 UI primitives. Behaviour and accessibility only — you bring the CSS.

Readme

isoline

Headless, unstyled Vue 3 UI primitives — behaviour and accessibility only. You bring the CSS.

npm install @monolithrobotics/isoline

What this is

A component here ships keyboard handling, focus management, ARIA wiring and state — and not one line of visual styling. It renders plain elements with data-* attributes you style yourself:

<script setup lang="ts">
import { ref } from 'vue'
import { SwitchRoot, SwitchThumb } from '@monolithrobotics/isoline'

const enabled = ref(false)
</script>

<template>
  <SwitchRoot v-model="enabled" name="notifications" class="my-switch">
    <SwitchThumb class="my-switch__thumb" />
  </SwitchRoot>
</template>

<style>
.my-switch {
  /* your design system, not ours */
}
.my-switch[data-state='checked'] {
  background: var(--accent);
}
.my-switch[data-disabled] {
  opacity: 0.5;
}
.my-switch__thumb[data-state='checked'] {
  transform: translateX(1rem);
}
</style>

Components with more than one moving part are compound: a root that owns the state and parts that read it, so every element between them is yours to write, style and place.

<script setup lang="ts">
import { ref } from 'vue'
import {
  SelectRoot,
  SelectTrigger,
  SelectPortal,
  SelectContent,
  SelectItem,
  SelectItemIndicator,
} from '@monolithrobotics/isoline'

const modes = [
  { value: 'auto', label: 'Automatic' },
  { value: 'manual', label: 'Manual' },
]
const mode = ref<string>()
const open = ref(false)
</script>

<template>
  <SelectRoot v-model="mode" v-model:open="open" name="mode">
    <!-- The library does not know your labels, so the trigger's content is yours. -->
    <SelectTrigger class="trigger">
      {{ modes.find((m) => m.value === mode)?.label ?? 'Pick a mode' }}
    </SelectTrigger>

    <SelectPortal>
      <SelectContent class="listbox">
        <SelectItem v-for="m in modes" :key="m.value" :value="m.value" class="option">
          {{ m.label }}
          <SelectItemIndicator>✓</SelectItemIndicator>
        </SelectItem>
      </SelectContent>
    </SelectPortal>
  </SelectRoot>
</template>

<style>
.option[data-highlighted] {
  background: var(--accent);
}
.listbox[data-side='top'] {
  transform-origin: bottom;
}
</style>

Why it exists

It was extracted from an internal application after PrimeVue moved to a commercial licence and archived its MIT line. That application already owned its whole visual layer and was overriding the library's theme everywhere it touched it — so what was actually being depended on was the behaviour underneath. This is that layer, on its own.

Scope, honestly: this is built for one application and released because there is no reason to keep it closed. It is MIT, use it freely — but it is not a funded open-source project. The API will move before 1.0, issues are answered when someone has time, and components land when that application needs them rather than to complete a set. If you want the same idea with support behind it, reka-ui is excellent and we would not be offended.

Components

| Component | Status | | --------------- | --------------------- | | Switch | ✅ | | Checkbox | ✅ | | RadioGroup | ✅ | | Toggle | ✅ | | Slider | ✅ | | NumberField | ✅ | | PasswordField | ✅ | | DatePicker | ✅ single date | | Select | ✅ single choice | | MultiSelect | ✅ | | Dialog | ✅ modal | | Tooltip | ✅ | | DataTable | ✅ sort, select, page | | Splitter | ✅ |

Planned

Ordered by how much behaviour there is to get wrong, which is the only reason any of these belongs in a library rather than in your own markup.

| Component | What it is for | | --------------------------- | ------------------------------------------------------------------- | | Popover | Anchored, non-modal, dismissible — a tooltip that can hold controls | | Menu | Roving focus, submenus, typeahead, checkbox and radio items | | ContextMenu | The same menu, opened by right-click at the pointer | | CommandMenu | Searchable palette over the same list behaviour | | Toast | A live region, a queue and timers that pause on hover | | Tabs | Roving tab stop over the tabs, panels wired by aria-controls | | Accordion | Disclosure with single or multiple open, and keyboard between heads | | Combobox | Select with a text input over it, including async suggestions | | Listbox | The list without the popup, for a panel or a sidebar | | Tree | Expand, collapse, typeahead over a hierarchy | | Paginator | Page arithmetic, ellipsis ranges, and the keyboard over them | | VirtualScroller | Windowed rendering, reusable by long Select and Tree lists | | Splitter | Draggable panes with keyboard resize and min/max | | FileUpload | Drag-drop target, type and size validation, per-file progress | | TagsInput | Tokens with backspace editing and paste splitting | | OtpInput | Focus that walks between boxes, and paste across all of them | | Rating | A radio group that reads as stars | | FocusTrap, DismissLayer | The pieces inside Dialog, exposed for building your own overlays |

Not planned, on purpose

A text input, a button, a textarea, a status message, a floating label, a card, a divider, a badge, a chip, a tag, an avatar, a skeleton, a progress bar or a spinner have no behaviour to extract — they are an element and some CSS. A library part for them costs an import, a layer of indirection and a version to track, and gives you nothing your own markup does not already do. Write them yourself.

Ripple effects, scroll-driven animation and class-toggling directives are a theme's job, not a behaviour library's.

Conventions

  • data-state over classes. Components never add or remove classes — yours are left exactly as you wrote them. State is readable as data-state="checked | unchecked", data-disabled and friends.
  • An absent attribute means absent. data-disabled is either an empty string or not rendered at all — never data-disabled="false", which every CSS attribute selector would match.
  • Your attributes land on the control. class, id, aria-label, data-testid — whatever you write on SwitchRoot or CheckboxRoot is placed on the <button> that is the control, never on the hidden input mirroring it for form submission. id there is what lets a sibling <label for> both name the control and forward its clicks. What the component owns — role, aria-checked, data-state — is not overridable: a switch that could be told to call itself a checkbox is a bug, not a feature. SelectRoot and MultiSelectRoot render no element of their own, so put attributes on SelectTrigger / MultiSelectTrigger instead.
  • v-model is one-way in, events out. A component never mutates its own prop; the parent stays the single owner of the value.
  • Compound components share state through context. A part rendered outside its root throws immediately with the fix in the message, rather than failing later on undefined.
  • A group is one tab stop. Where a set of controls belongs together — RadioGroup, Select — Tab enters the set once and the arrows move within it, which is what the native control does. One tab stop per option strands keyboard users in long lists.
  • Popups are portalled, positions are published. Anything that floats renders at the end of <body>, out of reach of an ancestor's overflow: hidden or transform. Where a position is continuous rather than a state — a slider's — it arrives as a CSS custom property (--isoline-slider-fraction, 0 to 1) so you decide whether it means left, height or a rotation.

Development

Node 24 — earlier majors cannot run the build (vite needs a node:util export added after 20.11), and CI verifies on 24.

npm install
npm run test       # vitest
npm run typecheck  # vue-tsc
npm run lint
npm run build      # vite lib build + bundled .d.ts

Vue is a peer dependency and is never bundled — two Vue runtimes in one app break provide/inject across the boundary.

Releasing

Published to npm as @monolithrobotics/isoline by .github/workflows/release.yml, which fires on a v* tag and nothing else. There is no publish token in this repo — the workflow authenticates to npm through GitHub OIDC (trusted publishing) and attaches a provenance attestation.

npm version minor          # or patch — pre-1.0, breaking changes go in minor
git push --follow-tags

The workflow re-runs lint, format, typecheck, test and build before publishing, and refuses a tag whose version disagrees with package.json.

Pre-1.0 versioning. The API moves before 1.0, so a minor bump may break you. Consumers should pin an exact version ("0.5.0", not "^0.5.0") and upgrade deliberately.

Trusted publishing is configured against this repository and release.yml, and the package requires two-factor authentication with bypass tokens disallowed — so a leaked token cannot publish, and a release that did not come from a tag on this repo cannot exist. 0.1.0 was published by hand to bootstrap that, and carries no provenance attestation; every release since goes through the tag.

Licence

MIT © Monolith Robotics