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

@panphora/bevel

v0.2.1

Published

Beveled controls in an editorial page. One stylesheet, one script, no build.

Readme

bevel

Beveled controls in an editorial page. One stylesheet, one script, no build.

Buttons, switches, radios and steppers with a real machined edge (light top and left, dark right and bottom, mitred corners), set in a page that reads like a broadsheet: IBM Plex Sans body at 17px, Newsreader headings, IBM Plex Mono for every label and figure. Committed component state lives in the markup and survives document.documentElement.outerHTML serialization. Browser resources such as upload Files, editor selection and pointer sessions are temporary. Built for self-saving HTML (Hyperclay, htmlclay, clayjs) and fine anywhere else.

The docs page is the spec: index.html shows every component with its markup. starter.html is a page to copy.

React applications can opt into Bevel for React, with React 19 and Base UI 1.8 parts for all 70 catalog families. Import @panphora/bevel/react/layout.css for functional layout, then optionally @panphora/bevel/react/theme.css and @panphora/bevel/react/fonts.css. React owns component state; the classic script is not part of the React entry points.

The 36 functional examples live in examples/components/, and 33 of them preserve the selected reference sources under examples/components/reference/. They cover local interactions, keyboard behavior, durable DOM state and application event boundaries. The HyperCMS and Quickcrop contexts use their real supplied peers. Component, integration, installed-package and browser checks pass; the independent implementation review is resolved. See the examples guide for local setup. The Datastar example in examples/datastar/ uses the self-hosted 1.0.3 bundle and a local demonstration server.

Use

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@panphora/[email protected]/bevel-fonts.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@panphora/[email protected]/bevel.css">
<script src="https://cdn.jsdelivr.net/npm/@panphora/[email protected]/bevel.js" defer></script>

Or npm install @panphora/bevel and import @panphora/bevel/bevel.css, @panphora/bevel/bevel-fonts.css and @panphora/bevel/bevel.js. The font files ship in the package with relative URLs, so a vendored copy works offline. To use Google Fonts instead, drop the fonts file and add:

<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:ital,wght@0,400;0,500;0,600;1,400&family=Newsreader:ital,opsz,wght@0,6..72,200..800;1,6..72,200..800&display=swap">

Put bevel-page on body for the column and the typographic defaults, then copy components from the docs page.

Icons

Bevel ships one semantic icon library from @panphora/bevel/icons. It has 124 public names and four source kinds:

  • 69 house icons use the 24 by 24 Bevel grid, a 1.6px stroke, square caps and miter joins.
  • 3 round heading icons use the same grid with a 1.5px stroke, round caps and round joins.
  • 51 phosphor icons use the Phosphor Regular drawings selected for their contexts.
  • 1 mark is the filled Hyperclay Local menu bar mark.

The first three kinds are one outline library. There is no filled icon style. The Hyperclay Local mark is the sole filled exception. Every public name resolves to one drawing, so consumers choose a semantic name rather than a source or weight. See the icon drawing guide and the recorded choices in DECISIONS.md.

Import bevelIcon when markup is the useful output:

import { bevelIcon } from '@panphora/bevel/icons'

const decorative = bevelIcon('check')
const labelled = bevelIcon('warning', { size: 20, label: 'Warning' })

size defaults to 16 and sets both dimensions. Without label, the result is hidden from assistive technology. With label, it has role="img" and the supplied aria-label. Every drawing inherits currentColor.

bevelIcon reaches every drawing through ICONS, so a bundle that imports it carries all of them. To bundle only the icons you use, import each drawing by name and render it with renderIcon. The export name is icon followed by the public name in PascalCase:

import { renderIcon, iconComment, iconChevronLeft } from '@panphora/bevel/icons'

const comment = renderIcon(iconComment, { size: 17 })

The @panphora/bevel/icons.svg export contains one symbol per name. Copy or serve it as an asset, then reference #bevel-icon-<name>:

<svg width="16" height="16" aria-hidden="true" focusable="false">
  <use href="/assets/bevel-icons.svg#bevel-icon-check"></use>
</svg>

React applications get the same names and accessibility behavior from the main React entry point:

import { Icon } from '@panphora/bevel/react'

<Icon name="check" />
<Icon name="warning" size={20} label="Warning" />

The icon guard scans Bevel source, React, integrations, the showcase and built examples. npm run check:icons reports inline drawings that do not match the library. npm run check runs the same guard in strict mode and fails when it finds one.

To add an icon:

  1. Choose one semantic public name and the drawing that reads best in its real context. Add a house path to icons/house.json, a rounded path to icons/round.json, or a public name to Phosphor file mapping in icons/phosphor.json. Product marks live in icons/marks/ and remain exceptional.
  2. Follow the icon drawing guide. Render the icon at its real size beside related library icons, then inspect it at 48px.
  3. Run npm run build:icons. This regenerates the ESM map and helper, declarations, sprite and React component.
  4. If the namespace changed, update the total and kind counts in tests/icons.mjs. If component markup changed, run npm run build to refresh the generated showcase and examples.
  5. Run npm run test:icons, npm run test:icon-guard, npm run build:check and npm run check.

Rules

  1. Bevel means pressable. Buttons, boxes, rings, thumbs, nubs, steppers and chips get the edge. Nothing else does.
  2. Fields are flat and recessed: a hairline box on the surface, a 2px brass focus ring.
  3. Ink acts, brass works, ox warns, teal confirms. One primary button per view.
  4. Anything with a digit is mono. Language is sans. Headlines are the serif.
  5. Square corners, no gradients. The radio ring is the exception.
  6. Twenty-seven colour tokens, cream by day and navy by night, every in-between state a color-mix(). Dark mode is light-dark() on the same names; .light and .dark force any subtree.
  7. One shadow tier.
  8. State lives in the markup. The script mirrors live values back into attributes.

Architecture

  • Components live in @layer components. Any utility or unlayered rule wins with no !important.
  • BEM with a bevel- prefix: bevel-button, bevel-button__label, bevel-button--primary.
  • bevel.js delegates interactions on document and initializes authored state on load. Call Bevel.refresh(root) after replacing markup and Bevel.destroy(root) before removing active components. Repeated script inclusion is guarded. Geometry tracking runs during active overlays and pointer sessions; optional integrations manage their own observers. Custom events: bevel:tags, bevel:reorder, bevel:tab, bevel:seg, bevel:remove, and on the data grid bevel:sort, bevel:select, bevel:expand, bevel:page, bevel:search.
  • No consumer build. Component sources generate the classic script, stylesheet and showcase with a small Node build.

Functional API

Load vendor/floating-ui/floating-ui.core.umd.min.js and vendor/floating-ui/floating-ui.dom.umd.min.js before bevel.js when a page uses anchored menus, pickers or confirmations. The core has no network dependency.

Bevel.open(element, { trigger }) and Bevel.close(element, { reason }) coordinate native dialog and manual popover state. Bevel.refresh(root) reconciles restored or replaced markup. Call Bevel.destroy(root) before removing an active subtree. Bevel.prepareForSave(cloneRoot) removes temporary UI from a save clone while retaining committed values. Bevel.notify(options) creates a local notification. Call Bevel.saved() only after an application save succeeds.

New mutating components emit cancelable bevel:beforechange, then bevel:change after a commit. Application operations emit bevel:action. Remote grids and boards emit bevel:request; the application applies authoritative content and calls Bevel.remote.resolve() to clear pending state.

Richclay, Quickcrop and Datastar remain optional. Load their adapters from integrations/ after bevel.js. The Richclay adapter exposes Bevel.richtext.mount() and unmount(). The Quickcrop adapter exposes Bevel.crop(). The Datastar adapter exposes Bevel.datastar.mount(), unmount(), control() and state().

Nested Datastar registrations retain their own sessions when an outer registration unmounts. The adapter passes the remaining roots through Bevel.destroy(root, { preserve: roots }); those subtrees and their owned rich editors are excluded from teardown. Ordinary Bevel.destroy(root) still releases the entire subtree.

For Richclay, load its Squire and DOMPurify scripts followed by its richclay.js global build before mounting. Bevel reuses externally mounted instances and never destroys them. It owns newly created instances even when the author supplied contenteditable. Repeated mounts reuse the instance; unmount releases its owned editor and theme tracking. A missing peer throws and emits bevel:error.

Use { inline: true, hyperclay: false } for an explicitly managed floating editor. Inside a native modal, use { inline: false, hyperclay: false, toolbarContainer }, with the toolbar container authored inside that modal beside the draft. Richclay keeps ownership of formatting, selection and link dialogs. Its body-level floating chrome receives scoped Bevel tokens without being relocated. For mounted editors, Bevel.prepareForSave(clone) invokes the actual peer's RichClay.stripFromClone(clone) cleanup through the adapter. A host managing Richclay without the Bevel adapter should call the peer cleanup directly. The live editor remains untouched.

Inline editing keeps the committed .bevel-inline-edit__value separate from its draft. Put a native input or textarea inside template[data-bevel-edit-template], with the class .bevel-inline-edit__input and its required/min/max/pattern constraints. An authored .bevel-inline-edit__editor contains the Save/Cancel controls and an optional [data-bevel-edit-slot] where Bevel inserts the temporary draft. Existing authored inputs are also supported.

Bevel.edit.start(root), commit(root) and cancel(root) return whether the operation succeeded. Save emits the cancelable edit transaction before changing canonical content. Escape cancels; Enter saves a single line, while textarea and multiline rich editing use Save or Ctrl/Cmd+Enter. A root with aria-readonly="true" or data-bevel-readonly refuses editing. [data-bevel-edit-error] displays validation messages; an optional child [data-bevel-error-message] preserves authored icons around the message.

Rich mode uses data-bevel-edit-kind="rich" and the optional Richclay integration. Add data-bevel-single-line for the chosen project-name editor, and data-bevel-placeholder on its draft element for an empty-state hint. The adapter owns the temporary peer instance and releases it on Save, Cancel or teardown. Multiline editors inside native modals keep their toolbar inside the modal. The inline-edit example loads consumer-supplied peers prepared through the example setup. Run npm run examples:peers in the checkout, or use node examples/setup-peers.mjs richclay /path/to/richclay in an extracted package. A committed edit updates the local document and does not claim a remote save.

For Quickcrop, load integrations/quickcrop.css with the core stylesheet and integrations/quickcrop.js after the core script. Import the consumer-supplied peer as an ES module before calling it:

<script type="module">
import quickcrop from './vendor/quickcrop/quickcrop.js'

export async function prepareImage(file, trigger) {
  const result = await Bevel.crop(file, { quickcrop, aspect: 1, trigger })
  if (!result) return null
  const data = new FormData()
  data.append('image', result.blob, 'cropped.png')
  return data
}
</script>

Call prepareImage with the original selected File or Blob and its trigger element. Cancellation returns null, so retain that original selection. Confirmation returns actual encoded dimensions and a Blob; it does not upload the image. The native crop modal honors Bevel close vetoes and returns focus to the trigger. An opening veto rejects the promise and releases the peer. Crop chrome is transient and removed from prepared snapshots. Quickcrop currently supports pointer geometry; centered presets can be confirmed by keyboard, but arbitrary keyboard rectangle movement is not available through its peer API.

For Clay or Hyperclay snapshot cleanup, load Bevel first, then register the clone transform. This handles an already loaded host, its readiness promise, or its ready event:

<script>
(() => {
  const registered = new WeakSet()
  const attach = (host) => {
    if (!host || registered.has(host)) return
    const register = host.onSnapshot || host.addDocumentTransform || host.beforeSave
    if (typeof register !== "function") return
    register.call(host, (clone) => Bevel.prepareForSave(clone))
    registered.add(host)
  }
  for (const name of ["clay", "hyperclay"]) {
    window.addEventListener(`${name}:ready`, () => attach(window[name]), { capture: true })
    attach(window[name])
    window[name]?.ready?.then(() => attach(window[name]))
  }
})()
</script>

Install this registration once per page. Capture listeners also receive nonbubbling ready events dispatched on document. For a host version without ready events or a readiness promise, run the registration after awaiting its module import. Snapshot hooks clean live sync frames. The fallback transform hooks clean saved documents only. Native Reset returns controls to their currently mirrored defaults, because committed changes update the default attributes.

The Datastar example seeds its application signals from the restored query input and hidden customer key on every cold load. The control adapter mirrors later user and signal changes into their saved attributes. Its initializer therefore remains valid after saving newer values, without serializing Datastar's entire signal store. The example also loads Floating UI for its combobox and sends the current values to a local demonstration handler.

node examples/datastar/server.mjs

The example opens at http://127.0.0.1:7384/. Its server acknowledges requests; persistence and business operations remain the application's responsibility.

Code and diff examples

The code block keeps its canonical source in one <pre><code> node. Optional line spans carry data-line for decorative numbers; those numbers are excluded from copied text and use an empty CSS text alternative. data-bevel-code-wrap toggles the durable data-bevel-wrap attribute. data-bevel-copy sends the exact code text to the Clipboard API. Rejection exposes a focused, selectable text field. Clipboard feedback is temporary and removed on reload, prepared snapshots and destroy.

The diff view takes authored records from <template data-bevel-diff-records>. Each record has a stable data-bevel-key, data-bevel-kind="same|add|remove", its applicable data-bevel-old and data-bevel-new line numbers, and a <code> child. Supplied <mark> spans retain word emphasis. Update the template content and call Bevel.refresh(root) to render new records. data-bevel-layout="unified|split" selects the presentation. Both layouts derive from that one template; Bevel does not calculate a text diff. Generated views can be discarded on save and rebuilt from the records.

Datastar may patch data-bevel-wrap and data-bevel-layout directly when its adapter is mounted. The adapter refreshes their derived controls without emitting a user change. Change data-bevel-revision after editing inert template content, or call Bevel.refresh(root) explicitly.

Develop

Author components in src/components/<name>/. Root index.html, bevel.css, bevel.js and the standalone component pages are generated. See the source guide for composition, shared helpers and ownership.

npm install
npm run fonts       # vendor the woff2 files out of the Fontsource packages
npm run check       # static rules: prefix, tokens only, square, coverage, ids
npm run contrast    # WCAG pairs in both modes
npm run roundtrip   # playwright: change every state on the docs page, save, reload, assert
npm run build       # generate the bundles and showcase
npm run dev         # build, watch and serve on :7383
npm run preview -- modal

npm test runs the original checks plus component, adapter, HTTP/SSE and snapshot regressions. The browser tests need npx playwright install chromium once. Run node tests/clay-snapshot.mjs for the documented registration against the pinned Clay snapshot fixture; full host boot and transport coverage remains separate.

License

MIT-0. Fonts are OFL-1.1 (see fonts/). The icons in icons/svg are Phosphor regular, MIT (see icons/LICENSE); they are in the repo for copying inline and are not part of the npm package.

Feedback and split panes

Bevel.notify({ key, title, message, tone, action, duration }) creates a transient toast in an authored .bevel-toasts host, or a fixed host when none exists. tone is info, success or error. Optional action: { id, label, icon } emits one bevel:action with the toast key before dismissing. The retry action mirrors its icon; icon: "retry" also applies that treatment to a custom action ID. Text is inserted as text, never HTML. An authored <template data-bevel-toast-template> can supply the documented toast anatomy.

Timed notices pause on hover, focus and document hiding. Action and error notices persist by default; duration: 0 keeps any notice open. Dismissal restores focus when necessary. Toasts and their timers are temporary: destroy, raw reload and prepared snapshots remove them. Bevel.saved({ duration }) shows the small Saved chip after your application reports success. Repeated saves restart its timer. A .bevel-action-banner instead represents an application condition; the application owns requests and resolution, while data-bevel-banner-dismiss records a vetoable durable dismissal.

The split panes and source/preview example use two .bevel-split__pane children separated by .bevel-split__divider. Give the separator an accessible label. data-bevel-split="32" stores the first pane's percentage; existing data-bevel-ratio markup is also supported. data-bevel-orientation="vertical" stacks resizable panes vertically. Optional data-bevel-min-first and data-bevel-min-second set minimum pane extents in CSS pixels, each defaulting to 180.

Pointer resizing previews the track size and commits on release. Escape, pointer cancellation, lost capture and destruction discard the preview. An authoritative external ratio cancels an older draft. Arrow keys move two percentage points, Shift moves ten, and Home/End use the current bounds. The separator exposes its orientation and numeric ARIA values. Bounds normally span 25 to 75 percent and honor physical minima; an insufficient container uses a fixed proportional allocation with scrolling inside the panes. Below 641 viewport pixels, panes stack and the separator disappears while retaining the desktop ratio. Update canonical attributes and call Bevel.refresh(root) after external changes; the Datastar adapter observes those attributes automatically.

Grid extensions

The filter builder, column manager, and editable cells use stable field and row keys. data-bevel-grid on a filter or manager names its target grid. Existing search, view filters, sorting and pagination continue to operate on the same rows.

Bevel.filter.read(root) returns committed { match, rules }. Bevel.filter.matches(record, expression, fields) evaluates those rules without a DOM. A field definition supplies id, label, type and optional enum { id, label } options. Supported types are text, number, date and enum. Dates are valid ISO calendar dates; blank or invalid numbers are not coerced to zero. Empty rules match all records. Apply validates before replacing committed rules; Cancel and a cold reload discard drafts.

Column headers own data-bevel-column, DOM order, hidden, data-bevel-pinned and data-bevel-width in CSS pixels. The manager derives its controls from those headers. Bevel.refresh(manager) reconciles body/footer cells, the colgroup, sticky offsets and legacy field indexes. Optional [data-bevel-column-pin] buttons inside manager items toggle left pinning. Required identity columns remain visible and pinned. Reordering, visibility, pinning and resizing use cancelable transactions; pointer preview does not change committed order.

Editable cells declare data-bevel-edit="text|number|enum", data-bevel-column and canonical data-value. Enum cells provide data-bevel-options containing { id, label } options. Enter or F2 starts editing; Save or Enter commits, Cancel or Escape discards, and Tab or Shift+Tab commits then moves to another visible editable cell. Validation and veto keep the draft open. The cell’s committed value remains separate from its temporary editor during snapshot cleanup.

Kanban state

Cards in keyed column lists own task content and order. data-bevel-assignee, data-bevel-priority, data-bevel-due and child data-bevel-label elements hold committed fields. Keyed native checkboxes in .bevel-kanban__checklist-data own checklist values. The inspector renders .bevel-kanban__check-item controls from those checkboxes; generated inspector content is temporary.

Filter selects and date inputs use data-bevel-kanban-filter with assignee, label, priority, due-from or due-to. The matching data-bevel-filter-* attributes on the board are committed values. Date ranges include both endpoints. Filtered boards retain full card order and expose Move controls while pointer reorder is disabled.

The shipped example fixes its demonstration date with data-bevel-today. Omit that attribute to compare due dates with the current local date. WIP limits use data-bevel-wip on a column and remain advisory. Board counts exclude archived cards. The generated inspector is rebuilt from the selected key after raw or prepared reload; unfinished task and checklist forms are discarded.