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

@videsigns/kit

v2.1.3

Published

ViDesigns Webflow behaviour library — modular, attribute-loaded, jsDelivr-served.

Downloads

710

Readme

VDS — Webflow Behaviour Library

A single, modular custom-code library for Webflow starter-kit and client projects. One script tag, served from jsDelivr, loads only the modules a given site flags via attributes. Behaviour attaches to broad data-attributes (vds-tabs, vds-slider, …) rather than component-specific classes, so one module drives unlimited components.

This document is the contract: it defines the attribute vocabulary, the module map, and what each module expects in the DOM. Build to this spec; treat divergence from it as a bug.

Namespace note: the prefix is vds- (from ViDesigns, matching the prefix you used in your first sketch). It's trivial to rename later — it's a single constant in the build — so don't over-think it now.


1. How it loads

One script tag goes in the Webflow project's footer custom code. The module attributes on the tag decide what runs.

<script async type="module"
  src="https://cdn.jsdelivr.net/npm/@videsigns/kit@2/dist/vds.js"
  vds-tabs
  vds-slider
  vds-theme
></script>
  • The src is a small loader. It reads its own attributes, then dynamically import()s only the flagged module chunks. Unflagged modules are never fetched or parsed. (See the separate architecture notes for the build/bundling details.)
  • @2 is a major-version range: patches and new features auto-propagate to every site without touching the Webflow project. A breaking change ships as v3, which @2 sites never pick up. This is the deliberate "auto-update the safe stuff, opt-in to the dangerous stuff" model for the starter kit.
  • jsDelivr caches floating ranges for up to ~7 days (browser cache stacks on top, ~14 days worst case). The release pipeline purges the jsDelivr URL on publish so updates land in minutes, not weeks.
  • async type="module" keeps the script non-render-blocking. Exception: the theme module — see §3.6 for the flash-of-wrong-theme handling, which needs a tiny synchronous snippet placed as the first element inside <body> (not deferred).

A module is active when its attribute is present on the script tag. Within the page, behaviour then attaches to any matching component automatically — every module is querySelectorAll-based and initialises all instances.


2. Attribute conventions

A consistent grammar across all modules:

| Form | Meaning | Example | |---|---|---| | vds-<module> on the script tag | Load this module | vds-slider | | vds-<module> on a component root | This element is a <module> instance | <div vds-slider> | | vds-<module>="<value>" on a root | Optional style/skin selector (CSS only; JS never branches on it) | vds-tabs="layout-5" | | vds-<module>-<option>="<value>" on a root | A config option | vds-tabs-interval="6000" | | vds-<module>-element="<role>" on a child | A structural part of the component | vds-slider-element="track" |

Two structural targeting rules, applied consistently:

  1. Where a native ARIA pattern exists, that is the structural contract — tabs use role="tablist" / "tab" / "tabpanel" plus aria-controls; accordion triggers/panels relate via aria-controls / aria-expanded. State lives in ARIA everywhere (aria-selected, aria-hidden, aria-expanded).
  2. Where no clean ARIA structure exists (slider track/arrows/dots, gallery track, video parts, filter buttons/items), use vds-<module>-element="<role>".

Value conventions:

  • Booleans accept "true" / "false". Every boolean documents its own default (they are not all default-false — see each module).
  • Durations are milliseconds unless noted. Marquee speed is in seconds (it reads naturally that way).
  • Selectors and key-lists are plain strings / comma-separated lists.

3. Modules

3.0 Shared core (always present)

Modules share a small core (downloaded once and reused). esbuild splits it into a few fine-grained chunks, so a module pulls only the parts it uses — most pull the observer; tabs/slider/accordion also pull the autoplay chunk; theme pulls none. It standardises the behaviours that were previously copy-pasted — and subtly inconsistent — across six scripts:

  • Autoplay controller. One implementation behind tabs, slider, and accordion autoplay. Consistent everywhere: pauses on hover (default on), pauses on keyboard focus (modality-aware — a mouse click never leaves a component stuck paused), pauses when the tab is hidden, and only runs while the component is in the viewport. Manual interaction resets the timer (or hard-stops it, per option). Pause/resume preserves elapsed time so the progress bar and timer stay in sync.
  • Reduced motion. All transitions and all autoplay respect prefers-reduced-motion: reduce (disables autoplay, collapses animation). Applied uniformly — previously only some scripts honoured it.
  • Init timing. Runs after DOMContentLoaded and after Webflow.push(...) settles, so it doesn't fight Webflow's own interactions.
  • CMS / dynamic DOM. One debounced MutationObserver, scoped as tightly as possible, that re-inits newly injected components and disconnects after a settle window. This replaces the two unthrottled page-wide observers in the current code (header-offset and utm), which forced layout / rewrote every link on every mutation.
  • Idempotent init. Modules guard against double-initialisation. Multiple instances per page are always supported.

Config options listed per module below are the per-component overrides; the autoplay defaults above are the shared baseline.


3.1 tabs

Absorbs layout-3, layout-4, layout-5, and the VI pill tabs. One engine that never branches on the skin name. Icon reveal is pure CSS (off aria-selected); the progress fill, sliding pill, and edge-bleed are opt-in capability flags (vds-tabs-progress, vds-tabs-pill, vds-tabs-breakout) — pill and breakout involve JS measurement, the rest is CSS.

Activate: vds-tabs on script + on each component root. Skin (optional): vds-tabs="layout-5" etc. — picks a CSS skin only.

Structure (ARIA):

| Element | Selector | |---|---| | Tab list | [role="tablist"] | | Tab | [role="tab"] with aria-controls="<panelId>" | | Panel | [role="tabpanel"][id] |

Active state is written to aria-selected (tabs) and aria-hidden (panels). No display:none on panels — visibility is via the attribute hooks so transitions work.

Options (root):

| Attribute | Type | Default | Notes | |---|---|---|---| | vds-tabs-autoplay | bool | false | auto-advance | | vds-tabs-interval | ms | 5000 | | | vds-tabs-animation | none\|slide-down\|carousel\|fade | slide-down | | | vds-tabs-progress | bool | false | show progress fill on active tab | | vds-tabs-pause-on-hover | bool | true | | | vds-tabs-pause-on-focus | bool | true | keyboard-modality-aware | | vds-tabs-stop-on-interaction | bool | false | hard-stop vs reset on click | | vds-tabs-viewport-threshold | float | 0.35 | autoplay only when this fraction is visible | | vds-tabs-breakout | bool | false | layout-5 edge-bleed measuring (opt-in; not inferred from skin) | | vds-tabs-pill | bool | false | measured sliding-pill highlight (VI tabs); JS positions it via CSS vars |

CSS hooks: --vds-tabs-interval (progress duration), [aria-selected], [aria-hidden], [data-tabs-dir="next\|prev"] (carousel direction), --vds-tabs-breakout-left/right, and (when vds-tabs-pill) a generated [vds-tabs-element="pill"] positioned via --vds-tabs-pill-x / --vds-tabs-pill-w on the tablist.

Keyboard: arrows / Home / End move focus; Enter / Space activate. Roving tabindex.


3.2 slider

Absorbs the single-slide and 3-card sliders. They were the same engine with perView = 1; per-view now comes from CSS, so one module covers both and any other count.

Activate: vds-slider on script + on each component root.

Structure:

| Element | Selector | Required | |---|---|---| | Track | vds-slider-element="track" | yes | | Slide | vds-slider-element="slide" | yes (≥2) | | Prev / Next | vds-slider-element="prev" / "next" | optional | | Dots wrap | vds-slider-element="dots" | optional (dots auto-generated) |

Per-view is CSS-driven: set --vds-slides-per-view on the track (default 1). Change it at breakpoints in CSS; JS rebuilds clones on change. This is the single/multi merge.

Options (root):

| Attribute | Type | Default | Notes | |---|---|---|---| | vds-slider-loop | bool | true | clone-based infinite loop | | vds-slider-swipe | bool | true | touch swipe | | vds-slider-autoplay | bool | false | | | vds-slider-interval | ms | 4000 | | | vds-slider-pause-on-hover | bool | true | standardised to true (was false in old code) | | vds-slider-avatars | bool | false | opt-in avatar-thumbnail sync add-on |

Avatar add-on (when enabled): thumbnails carry vds-slider-element="avatar" and vds-slider-avatar-index="<n>"; active gets .is--current.

CSS hooks: --vds-slides-per-view, .is-animating on track, .is-active on dots; clones are marked with the data-vds-clone attribute (target [data-vds-clone]).

Slide sizing is the CSS's job. The module reads --vds-slides-per-view only to know which slide to move to; it does not size the slides. For any multi-per-view slider you must give each slide its width from the same variable, and the between-slide gap you set must be the same value the width calc subtracts (use one gap source — the track's gap — or the row overflows):

.my_track { display: flex; gap: var(--slide-gap, 1.5rem); }
.my_track > [vds-slider-element="slide"] {
  flex: 0 0 auto;
  box-sizing: border-box;
  width: calc((100% - (var(--vds-slides-per-view) - 1) * var(--slide-gap,1.5rem)) / var(--vds-slides-per-view));
}

Single-slide sliders (per-view 1) need none of this.


3.3 accordion

The FAQ accordion and the testimonial-7 rotator — they're the same single-active-with-optional-autoplay machine. Rotator = accordion with autoplay + single + always-open.

Activate: vds-accordion on script + on each component root.

Structure:

| Element | Selector | |---|---| | Item | vds-accordion-element="item" | | Trigger (button) | vds-accordion-element="trigger" — gets aria-expanded, aria-controls | | Panel | vds-accordion-element="panel" — gets role="region", aria-labelledby |

Options (root):

| Attribute | Type | Default | Notes | |---|---|---|---| | vds-accordion-single | bool | false | one open at a time | | vds-accordion-duration | ms | 300 | open/close animation | | vds-accordion-always-open | bool | false | a click can't close the last-open item (rotator behaviour) | | vds-accordion-autoplay | bool | false | auto-advance through items | | vds-accordion-interval | ms | 4000 | |

Item initial state: vds-accordion-open="true" on an item opens it on load.

CSS hooks: .is-open on item, [aria-expanded], [hidden] on collapsed panels (when not animating).

Note: this fixes the current testimonial-7 bug where only the first component on a page initialises (querySelectorquerySelectorAll).


3.4 gallery

The drag-with-momentum slider and the marquee. Kept separate from slider because free-drag and infinite marquee are genuinely different from slide-snap, though it still consumes the shared core.

Activate: vds-gallery on script + on each component root.

Structure: vds-gallery-element="track" wrapping the items. Optional [aria-live] region for slider-mode announcements.

Options (root):

| Attribute | Type | Default | Notes | |---|---|---|---| | vds-gallery-mode | slider\|marquee | slider | runtime-switchable | | vds-gallery-speed | seconds | 24 | marquee loop duration (was hardcoded in CSS) |

CSS hooks: --vds-marquee-shift and --vds-marquee-duration (both set by JS — your marquee CSS must read animation-duration: var(--vds-marquee-duration, 24s) and translate by var(--vds-marquee-shift), or vds-gallery-speed has no effect), .is-dragging (slider drag), mode selector [vds-gallery-mode="marquee"]. Clones get [data-vds-clone].


3.5 filter

The blog category filter. Generalised and made robust: matching is on explicit values, not scraped tag text or button labels (the current approach is fragile and not CMS-safe).

Activate: vds-filter on script + on each component root.

Structure:

| Element | Selector | Notes | |---|---|---| | Filter button | vds-filter-element="button" (+ optional vds-filter-value) | value from vds-filter-value, else the button's own label text | | Item | vds-filter-element="item" | a filterable item | | Item categories (option A) | vds-filter-category="marketing, design" on the item | explicit comma list (static / single-value CMS) | | Item categories (option B) | nested vds-filter-element="tag" elements inside the item | reads their text — for Webflow multi-reference tag lists | | Empty state | vds-filter-element="empty" | shown when nothing matches |

Options (root):

| Attribute | Type | Default | Notes | |---|---|---|---| | vds-filter-match | any\|all | any | OR vs AND across active filters |

CSS hooks: .is-active on buttons, [aria-pressed], [hidden] on filtered-out items.


3.6 theme

Light/dark. The one render-affecting module, so it needs special handling to avoid a flash of the wrong theme (FOUC).

Designed for Webflow Variable Modes. The dark class is applied to <body> by default, because Webflow generates its Dark variable-mode CSS scoped to the body element (you can't apply a variable mode to <html> in the Designer). So body.is-dark is what flips your variable collection to Dark and re-colours the page. Do not move this to <html> unless you've abandoned Webflow variable modes and hand-write your dark token values in custom CSS.

Two parts:

  1. Pre-paint snippet (synchronous, not deferred) — a ~10-line blocking script placed as the first element inside <body> (an HTML Embed, ideally in a global symbol on every page). It reads forced/stored/system preference and sets body.is-dark before the page content paints. It must sit at the top of the body, not the <head> (where <body> doesn't exist yet) and not the footer (too late). This is the only inline code the kit requires. See snippets/theme-inline.html.
  2. theme module (deferred, in the bundle) — wires up the toggles, system-change listener, and storage after load. The class is already correct from step 1, so there's no flash.

Activate: vds-theme on script.

Controls (in page):

| Attribute | Element | |---|---| | vds-theme-toggle | toggle button (gets aria-checked) | | vds-theme-auto | "reset to system" control |

Options (on the <script> tag):

| Attribute | Type | Default | Notes | |---|---|---|---| | vds-theme-default | system\|light\|dark | system | initial when nothing stored | | vds-theme-force | light\|dark | — | build-time lock; ignores system/storage/toggles (replaces the FORCE_THEME string flag) | | vds-theme-storage-key | string | theme | | | vds-theme-class | string | is-dark | class applied for dark | | vds-theme-target | body\|html | body | body drives Webflow variable modes (default). html only for non-variable-mode setups that style off :root/html. |

If you change vds-theme-force / vds-theme-storage-key / vds-theme-class on the script tag, mirror the same values in the pre-paint snippet's consts so the two agree.


3.7 utm

Persists UTM + click-ID params in sessionStorage and applies them to internal links, hidden form fields, and optional external CTAs. Page-global, so configured on the script tag, not a component.

Activate: vds-utm on script.

Options (script tag):

| Attribute | Type | Default | Notes | |---|---|---|---| | vds-utm-keys | comma list | the 8 standard keys¹ | params to track | | vds-utm-attribution | first\|last | last | first-touch vs last-touch | | vds-utm-storage-key | string | vd_persisted_params_v1 | | | vds-utm-internal-links | bool | true | append params to internal <a> | | vds-utm-forms | bool | true | fill hidden inputs named like the keys | | vds-utm-cta-selector | string | — | external CTA buttons to rewrite (opt-in) | | vds-utm-cta-base | url | — | base URL for those CTAs |

¹ utm_source, utm_medium, utm_campaign, utm_term, utm_content, gclid, fbclid, msclkid

Performance fix vs current code: link decoration no longer runs a page-wide MutationObserver on every DOM change. It decorates on load and re-decorates via the shared debounced observer (and lazily on click for late-added links).


3.8 video

Generalised from testimonial-9 to any video: click-to-play with overlay, native controls on play, overlay restored on end.

Activate: vds-video on script + on each wrapper.

Structure:

| Element | Selector | |---|---| | Wrapper | vds-video (root) | | Play trigger | vds-video-element="trigger" | | Overlay | vds-video-element="overlay" | | Video | vds-video-element="player" (or a <video> inside) |

No config beyond presence.


3.9 site (text + condition cleanup + header offset)

The global bits that don't belong to a component. Split so they can be toggled independently if wanted.

vds-text — text injection into [vds-text="<key>"]. year is a built-in dynamic token (current year). Arbitrary values (company name, review score/count) are read from a site-config object set once per project:

<script>window.VDS_TEXT = { company: "ViDesigns", "review-score": "4.5", "review-count": "129" };</script>

Also removes Webflow's .w-condition-invisible empty-state nodes.

vds-header-offset — sets --header-h to the max bottom edge of nav/banner.

| Attribute | Type | Default | |---|---|---| | vds-header-offset-nav | selector | .navbar_container, .w-nav | | vds-header-offset-banner | selector | .banner_container | | vds-header-offset-var | string | --header-h |

Uses the shared debounced observer rather than the current unthrottled one.


4. Module map (summary)

| Module | Script attr | Replaces | |---|---|---| | tabs | vds-tabs | layout-3, layout-4, layout-5, VI pill tabs | | slider | vds-slider | single-slide slider, 3-card slider | | accordion | vds-accordion | FAQ accordion, testimonial-7 rotator | | gallery | vds-gallery | drag-slider + marquee gallery | | filter | vds-filter | blog category filter | | theme | vds-theme | light/dark toggle | | utm | vds-utm | param persistence / link & form decoration | | video | vds-video | testimonial-9 video control | | site | vds-text, vds-header-offset | global text inject, condition cleanup, header offset |

14 scattered scripts → 10 modules (the site row below is really two: text + header-offset) + a shared core (split into a few small chunks).


5. Migration map (old → new)

For reference when porting existing components onto the new contract.

| Old | New | |---|---| | data-tabs="layout-3\|4\|5", data-vi-tabs | vds-tabs (+ optional skin value) | | data-layout3-auto, data-tabs-auto, data-tabs-auto-rotate | vds-tabs-autoplay | | data-layout3-interval, data-tabs-interval | vds-tabs-interval | | data-slider, data-slider-multi | vds-slider (+ --vds-slides-per-view) | | data-loop, data-autoplay, data-autoplay-delay, data-pause-on-hover | vds-slider-loop, vds-slider-autoplay, vds-slider-interval, vds-slider-pause-on-hover | | [data-slider-prev/next], .slider_track, .slider_slide, .slider-dots | vds-slider-element="prev/next/track/slide/dots" | | [data-accordion-item/trigger/panel] | vds-accordion-element="item/trigger/panel" | | .testimonial7_component (rotator) | vds-accordion + autoplay + single + always-open | | data-gallery-mode | vds-gallery-mode | | .blog_tabs + scraped tag/label text | vds-filter + vds-filter-value / vds-filter-category | | data-theme-toggle, data-theme-auto, FORCE_THEME | vds-theme-toggle, vds-theme-auto, vds-theme-force | | .testimonial9_* | vds-video-element="…" | | [data-text] | vds-text + window.VDS_TEXT |


6. Versioning & releases (summary)

  • Semantic versioning. Patches/minors are backward-compatible and auto-propagate via the @2 range.
  • Breaking changes → new major (v3); existing sites stay on @2 until deliberately migrated.
  • CI on a version tag: build → publish to npm → purge jsDelivr URLs.
  • Test on a canary site before publishing; a bad release reaches all @2 sites once the cache turns over.

(Full build/bundling and CI details live in the architecture notes, written next.)