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

@kubed.io/cdn

v0.0.8

Published

A theme layer and web components for Grafana panels, loaded by URL from jsDelivr.

Readme

📦 cdn

A theme layer and web components for Grafana panels. Shared CSS and ES modules that dashboards load by URL instead of carrying copies: the npm package @kubed.io/cdn, served by jsDelivr. 🎨

🧪 Test 🛡️ Quality


🗂️ What is here

| Entry | What it brings | |---|---| | dist/kd.js | 🎨 the core: the theme layer, the Grafana page helpers, and every generic element | | dist/kd.css | text, links, tables, badges, code, cards, and column/tile/part layouts for plain .kd markup | | dist/openapi.js | 📜 <kd-schema>: any OpenAPI v3 or JSON Schema, kubectl-explain style, rendered lazily | | dist/k8s.js | ☸️ Kubernetes objects mapped onto the core elements: <kd-k8s-object>, <kd-k8s-ref>, <kd-k8s-events> | | dist/n8n.js | 💬 mount(config): the n8n agent chat tile for app dashboards | | dist/github.js | 🐙 GitHub glue: a git tree for <kd-files>, blob/raw/README URLs, code-server activity and Claude edits per file |

The entries share chunks, so a page that loads several pays for Lit and the core once. The look lives in the core; a domain entry adds behaviour, never colours.

Elements (all kd-*, real custom elements with shadow DOM, themed from --kd-*):

| | | |---|---| | kd-pill kd-tile kd-link kd-mask kd-reveal | small things: a toned pill, a header tile, a dashboard link that keeps the time range and a back chain, a click-to-reveal value, one toggle for every masked value around it | | kd-bar kd-sheet kd-table | a title bar with chips and a breadcrumb, a property sheet (or a grid of label-over-value tiles), a table with optional row headers; cells can be text, pills, links, code or data | | kd-status kd-card | the health of anything as a headline and toned check badges; a bordered block with a tone accent, a header, a state line and a body | | kd-groups kd-data | labels as grouped pills, annotations as a tree; any JSON value as folded YAML | | kd-tabs kd-accordion kd-split | tabs that remember their choice, collapsible sections on one page, a list beside the selected item | | kd-steps kd-meter | a stepper, a value against request/limit marks | | kd-markdown kd-code | GitHub-flavoured markdown, sanitised; syntax highlighting with line numbers, in 190+ languages loaded one at a time | | kd-files kd-file | a file explorer over any provider (a static list today, WebDAV later) that can drive a dashboard variable; one file shown with the right renderer |

The source is TypeScript under src/, one folder per entry. dist/ is built by CI and exists only in the published package.

🔗 The URL

https://cdn.jsdelivr.net/npm/@kubed.io/[email protected]/dist/<file>

An exact version is cached for a year and can never change underneath a dashboard. Never a range like @0 in a dashboard: it is stale for up to a week and moves without notice.

Each dashboard holds the base URL once, in a hidden constant variable named assets:

- kind: ConstantVariable
  spec:
    name: assets
    query: https://cdn.jsdelivr.net/npm/@kubed.io/[email protected]/dist
    hide: hideVariable

A release is then a one-line change per dashboard. Move every dashboard together: panels share one page, and the first version of a custom element loaded owns its tag until a full reload.

🎨 The theme

applyTheme sets these on the panel root, plus data-theme="dark" or "light". The names are the public API and follow semver.

| Property | From GrafanaTheme2 | |---|---| | --kd-bg, --kd-bg-2, --kd-canvas | colors.background.primary, .secondary, .canvas | | --kd-text, --kd-text-2, --kd-text-dim | colors.text.primary, .secondary, .disabled | | --kd-link | colors.text.link | | --kd-border, --kd-border-strong | colors.border.weak, .medium | | --kd-primary, --kd-success, --kd-warning, --kd-error, --kd-info | colors.<name>.main | | --kd-font, --kd-font-mono | typography.fontFamily, .fontFamilyMonospace | | --kd-radius | shape.radius.default | | --kd-space | spacing(1) |

Everything is scoped under .kd. Business Text loads stylesheets into the whole page, so no rule here touches an element, * or :root outside a .kd root.

🧩 In a Business Text panel

externalStyles: [{ id: "kd", url: "${assets}/kd.css" }]   # objects: a bare string never loads
content:        <div class="kd"> ... </div>
afterRender:    import(context.grafana.replaceVariables("${assets}") + "/kd.js")
                  .then(m => m.applyTheme(context.element, context.grafana.theme))

afterRender runs on every re-render and applyTheme is idempotent, so switching Grafana's theme restyles the panel. Code with no context.grafana.theme calls followTheme(element) instead: it reads the live theme from Grafana's runtime and follows a theme switch.

Every panel imports what it needs itself. The browser loads a URL once per page however many panels import it, and an element defined by one panel works in all of them.

Feeding an element. Elements draw from their data and nothing else, because Business Text recreates them whenever the panel's HTML changes. Push data in from afterRender on every render:

content:        <div class="kd"><kd-table></kd-table></div>
afterRender:    import(base + "/kd.js").then(m => {
                  m.applyTheme(context.element, context.grafana.theme)
                  context.element.querySelector("kd-table").data = { rows: context.data }
                })

Markup from a query can carry the data as a JSON attribute instead, escaped: data="{{kdjson rows}}" with the kdjson helper (registerHelpers(context.handlebars)), or @json | @html in Infinity jq. Never {{{ }}} JSON into an attribute.

Or let feed do it - it themes the panel and hands each element its data in one call, and elements write dashboard variables themselves:

content:     <kd-files variable="file"></kd-files>
afterRender: import(base + "/kd.js").then(m => m.feed(context, { "kd-files": { entries: context.data } }))

Ready-made panels are in examples/.

🛠️ Develop

npm ci
npm run typecheck
npm test
npm run build

Unreleased code is tried in Grafana by publishing a pre-release (a pre* bump in the 🧬 Publish Version workflow), which goes to npm under the next tag.