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

@kraftwerkdesign/kd-components

v0.3.2

Published

Native web components for Kraftwerk agency projects.

Readme

@kraftwerkdesign/kd-components

Native web components for Kraftwerk agency projects. No runtime framework — every component is a plain HTMLElement subclass that ships as ESM with TypeScript types.

Install

npm install @kraftwerkdesign/kd-components

Usage

Components are not registered automatically on import. You choose how they get into the custom-element registry.

Register everything

import '@kraftwerkdesign/kd-components/auto'

Register a single component

import '@kraftwerkdesign/kd-components/lite-vimeo'
import '@kraftwerkdesign/kd-components/lite-youtube'

Register manually (tree-shakeable, no side effects)

import { defineLiteVimeo, defineLiteYouTube } from '@kraftwerkdesign/kd-components'

defineLiteVimeo() // registers <lite-vimeo>
defineLiteYouTube() // registers <lite-youtube>

Each define* helper is idempotent (guarded by customElements.get) and accepts an optional custom tag name:

defineLiteVimeo('kd-vimeo')

The raw classes are exported too, if you need to subclass or register them yourself:

import { LiteVimeoEmbed, LiteYTEmbed } from '@kraftwerkdesign/kd-components'

Cookie consent gating

Both components expose two optional static hooks so you can block the third-party iframe (and its cookies) until the visitor has granted consent. Wire them once from your app entry — if left unset, the components load normally.

import { LiteVimeoEmbed, LiteYTEmbed } from '@kraftwerkdesign/kd-components'

for (const El of [LiteVimeoEmbed, LiteYTEmbed]) {
  // Checked on click. May open your consent UI. Return true to allow the load.
  El.consentGate = () => requireConsent('functionality')
  // Checked on autoload / scroll-into-view. Must NOT prompt — just report state.
  El.consentReady = () => hasConsent('functionality')
}
  • consentGate?: () => boolean — runs when the user activates the placeholder (click / Enter / Space). Return false to suppress the iframe; this is the place to trigger your consent modal.
  • consentReady?: () => boolean — runs for passive triggers (autoload, IntersectionObserver). Returns false to defer loading without prompting; the element stays observed, so a later grant lets the next intersection load it.

Blocked-consent notice

When a click is blocked (consentGate returns false), the component shows a dismissible notice pinned to the bottom of the poster explaining why the video did not load. It carries a default message and a close button out of the box:

This video is blocked until you accept cookies.

The notice is a click-gate affordance only. Passive triggers that fail consentReady (autoload, scroll-into-view) defer silently and never show it — they leave the poster untouched so a later consent grant can load the video on the next intersection.

Lifecycle. The notice is toggled by the consent-blocked class on the internal #frame. It appears on a blocked click and hides again automatically when:

  • the visitor dismisses it with the close button,
  • consent is later granted and the iframe successfully loads, or
  • the videoid / playlistid changes.

Dismissing it does not grant consent — the next click re-runs consentGate and re-shows the notice if still blocked.

Override the message with the consent-notice slot (links and markup are allowed):

<lite-vimeo videoid="76979871">
  <p slot="consent-notice">Accept cookies to watch. <a href="/privacy">Learn more</a></p>
</lite-vimeo>

Theme it with custom properties set on the host element:

| Custom property | Default | Controls | | ------------------------- | ------------------- | ------------------ | | --consent-notice-bg | rgb(0 0 0 / 0.82) | Overlay background | | --consent-notice-color | #fff | Text color | | --consent-notice-radius | 6px | Corner radius |

For anything the custom properties don't cover, the notice and its close button are exposed as shadow parts:

lite-vimeo::part(consent-notice) {
  font-size: 16px;
}
lite-vimeo::part(consent-notice-close) {
  /* custom close-button styling */
}

Accessibility. The notice container is a role="status" live region, the close button carries aria-label="Dismiss" and a visible :focus-visible outline, and a forced-colors fallback swaps to system Canvas/CanvasText colors with a border.

Components

Lightweight, fast-painting video embeds that only load the real iframe on interaction (or when scrolled into view with autoload). Both components share the same core API:

| Attribute | Description | | --------------- | --------------------------------------------------------------------- | | videoid | Video ID (required) | | videotitle | Accessible title used for the play button, poster and iframe | | videoplay | Localized "Play" label (default Play) | | videostartat | Start time in seconds, e.g. 30 (Vimeo also accepts 1m30s) | | params | Extra query params for the player URL, e.g. controls=0&rel=0 | | posterloading | loading attr for the poster image (default lazy) | | autoload | Load the iframe when scrolled into view via IntersectionObserver | | autoplay | Autoplay (muted) when injected via autoload; clicks always autoplay |

Provide a custom poster with the poster slot — the component then skips its own thumbnail requests entirely:

<lite-vimeo videoid="76979871">
  <img slot="poster" src="/my-poster.jpg" alt="" />
</lite-vimeo>

When the real iframe is injected, the element dispatches a bubbling custom event (liteVimeoIframeLoaded / liteYoutubeIframeLoaded) with the video id in event.detail.videoId.

If your Content-Security-Policy requires nonces on inline styles, set window.liteVimeoNonce / window.liteYouTubeNonce before the elements are registered.

Styling the play button

Both components share one play button (a dark disc, thin ring, and white triangle). Recolor and resize it with CSS custom properties set on the element:

| Custom property | Default | Controls | | ----------------- | ------------------------ | ------------------- | | --play-btn-fill | rgb(0 0 0 / 0.68) | Disc fill | | --play-btn-ring | rgb(166 145 95 / 0.74) | Disc outline ring | | --play-btn-icon | #fff | Play triangle | | --play-btn-size | 130px | Button width/height |

lite-youtube {
  --play-btn-fill: rgb(255 255 255 / 0.9);
  --play-btn-ring: #f00;
  --play-btn-icon: #f00;
  --play-btn-size: 90px;
}

For anything the custom properties don't cover, the button is exposed as a shadow part — reach it via ::part(play-button) (including states like ::part(play-button):hover):

lite-vimeo::part(play-button) {
  box-shadow: 0 0 0 4px #fff;
}

<lite-vimeo>

<lite-vimeo videoid="76979871" videotitle="The Mountain"></lite-vimeo>

| Attribute | Description | | ----------- | ------------------------------ | | videohash | Private-video hash (h param) |

The player URL always includes dnt=1 (Do Not Track).

<lite-youtube>

Adds playlist and YouTube Shorts support:

<lite-youtube videoid="dQw4w9WgXcQ" videotitle="Never Gonna Give You Up"></lite-youtube>

| Attribute | Description | | --------------- | --------------------------------------------------------------- | | playlistid | Playlist ID (used instead of videoid) | | posterquality | Thumbnail quality, e.g. hqdefault, maxresdefault | | nocookie | Use youtube-nocookie.com (default; set ="false" to opt out) | | short | Enable YouTube Shorts styling/behavior on mobile |

Development

npm install
npm run dev      # serve demo/index.html with Vite
npm run build    # build the publishable library into dist/
npm run format   # prettier

Publishing

Publishing is automated with npm OIDC trusted publishing — no NPM_TOKEN secret, and provenance is attached automatically. Run one release script; it bumps the version, tags it, pushes, and creates a GitHub Release, which triggers the publish.yml workflow to typecheck, build, and publish to npm.

npm run release:patch   # 0.1.0 -> 0.1.1
npm run release:minor   # 0.1.0 -> 0.2.0
npm run release:major   # 0.1.0 -> 1.0.0

One-time setup: on npmjs.com, open the package settings and add a Trusted Publisher pointing at the Kraftwerk-Design/kd-components repo and the publish.yml workflow. Until the package exists on npm, the very first publish may need to be run manually (npm publish) or the trusted publisher configured ahead of time under the org.

License

MIT. The lite-youtube and lite-vimeo components are derived from the MIT-licensed justinribeiro/lite-youtube and slightlyoff/lite-vimeo.