@vstx/vxcookies
v1.1.8
Published
Cookie consent via init() only (<vx-cookies> web component). Default entry: React as peer dependency. Use @vstx/vxcookies/standalone when the host has no React.
Maintainers
Readme
@vstx/vxcookies
A cookie consent UI and runtime for web applications. It ships as a custom element (<vx-cookies>) backed by React, exposes a single integration surface via init(), and persists choices in a cc_cookie JSON format compatible with CookieConsentHelper (PHP) and similar stacks.
Features
- One-shot integration — call
init()once; the library registers the web component and appends<vx-cookies>todocument.body. - Two distribution builds — use your app’s React 18+ (smaller, single React instance) or a standalone bundle with embedded React (no React required in the host). Standalone ships as ESM/CJS and as an optional IIFE global (
window.VXCookies) for pages without<script type="module">. - Locales — German, English, and Spanish (
translations.json+AvailableLocales). - Themes — dark (default) and light (
AvailableThemes). - Categories — necessary, functional, analytics with toggles and settings modal.
- Google tag / Consent Mode — updates
gtag('consent', 'update', …)when analytics is allowed. - Deferred third-party scripts — scripts tagged with
data-cookiecategoryrun after the user allows that category. - Global helper API —
window.__cookieConsentApiforallowedCategoryand reopening settings. - Styles — CSS is prepended into the JS bundle at build time (injects a
<style data-vxcookies>tag). Separate.cssfiles are also published if you prefer explicit imports.
Installation
npm install @vstx/vxcookiesPeer dependencies (default entry)
The main package entry expects React 18+ and React DOM 18+ in the consuming application (uses react-dom/client).
| Package | Version |
|---------------|-----------|
| react | >=18.0.0 |
| react-dom | >=18.0.0 |
Peers are marked optional in peerDependenciesMeta for standalone-only consumers, but npm may still report ERESOLVE if your tree has an older React (e.g. 16). In that case either:
- import
@vstx/vxcookies/standaloneand install withnpm install @vstx/vxcookies --legacy-peer-deps, or - align the host app on React 18+ and use the default entry.
Quick start
React 18+ application (recommended)
import vxcookies from '@vstx/vxcookies';
vxcookies.init({
locale: vxcookies.AvailableLocales.EN,
theme: vxcookies.AvailableThemes.DARK,
platform: {
name: 'My Product',
logoUrl: '/assets/logo.svg',
},
onFirstAccept: (data) => {
console.log('Accepted categories:', data.accepted_categories);
},
onPreferencesChange: (consent, changedCategories) => {
console.log('Consent updated', consent, changedCategories);
},
});No React (or React < 18)
import vxcookies from '@vstx/vxcookies/standalone';
vxcookies.init({
/* same options as above */
});Integración sin bundler (script clásico)
Cuando el HTML principal no usa módulos ES (p. ej. plantillas PHP que solo insertan <script src="…"> en el body), copia dist/standalone/vxcookies.standalone.iife.js a tu estático público (o instala el paquete y sirve node_modules/@vstx/vxcookies/dist/standalone/vxcookies.standalone.iife.js). El fichero es autocontenido (React incluido), minificado en el build de producción, expone window.VXCookies con la misma API que el default export del standalone ESM (init, AvailableLocales, AvailableThemes, CookieCategories, etc.) e inyecta los estilos con data-vxcookies igual que el bundle ESM.
<script src="/ruta/publica/vxcookies.standalone.iife.js"></script>
<script>
VXCookies.init({
locale: VXCookies.AvailableLocales.DE,
platform: { name: '…', logoUrl: '…' },
});
</script>ESM (@vstx/vxcookies/standalone) sigue siendo la opción habitual con bundler o con <script type="module"> y URL publicada. IIFE (@vstx/vxcookies/standalone/iife) encaja en hosts legacy sin resolver import desde node_modules.
CommonJS
const vxcookies = require('@vstx/vxcookies');
vxcookies.init({ /* … */ });Package exports
package.json exports (all paths are relative to the package root):
| Subpath | Description |
|-----------------------------|--------------------------------------------------|
| @vstx/vxcookies | ESM/CJS external build (peer React 18+) |
| @vstx/vxcookies/standalone| ESM/CJS standalone build (bundled React) |
| @vstx/vxcookies/standalone/iife | IIFE standalone (window.VXCookies), script clásico |
| @vstx/vxcookies/style.css | External build stylesheet |
| @vstx/vxcookies/standalone/style.css | Standalone stylesheet |
Artifacts live under dist/external/ and dist/standalone/ in the published tarball (incl. vxcookies.standalone.iife.js).
Public API
The default export is a plain object:
{
init(options?: InitOptions): { destroy(): void } | undefined,
// constants (see below)
}There is no supported public React component export; integration is init() only.
init(options)
Runs when the document is ready. If document.readyState === 'loading', it defers until DOMContentLoaded.
- Registers the custom element
vx-cookies(if not already defined). - Creates
<vx-cookies>, setsdisplay: contents, mounts the React tree inside a childdiv, and appends the element todocument.body. - If a
vx-cookiesnode already exists, logs a warning and returns without duplicating.
Return value: mountWebComponent() returns { destroy() } to remove the host node from the DOM (advanced use).
InitOptions
| Option | Type | Description |
|--------|------|-------------|
| locale | string | Static locale (de | en | es). Use AvailableLocales.*. |
| getLocale | () => string | Dynamic locale; overrides locale when set. |
| platform | { name: string, logoUrl: string } | Branding in the banner/modal. |
| getPlatform | () => object | Dynamic platform; overrides platform when set. |
| theme | string | dark or light (AvailableThemes). |
| getTheme | () => string | Dynamic theme. |
| breakpoints | { mobile?: number, … } | Merged with defaults (DefaultBreakpoints); drives responsive layout. |
| dataProtectionLegalTextGetter | () => string \| Promise<string> | HTML shown in the in-banner data protection panel. Optional — if omitted, the panel stays empty and no promise is rejected. |
| onFirstAccept | (data: { accepted_categories: string[] }) => void | Invoked when consent is applied and the analytics category is among the accepted categories (see implementation in VXCookiesContext). |
| onPreferencesChange | (consent, changedCategories: string[]) => void | Fired after the user saves settings in the modal if at least one category changed. |
Exported constants
Re-exported on the default object from constants.js:
CookieCategories—NECESSARY,FUNCTIONAL,ANALYTICS(string values match cookie payload).CookieSameSiteAttributes—LAX,NONE(internal cookie write helper).AvailableLocales—DE,EN,ES.AvailableThemes—DARK,LIGHT.DefaultBreakpoints—mobile,tablet,desktop(px).
Architecture (high level)
src/index.js → default export: { init, …constants }
src/standalone/index.js → same public API; standalone library entry (bundled React)
src/config.js → runtime config + init(), DOM guards
src/web-component/ → <vx-cookies> + createRoot mount
src/components/ → VXCookies modal, consent, settings, primitives
src/context/ → VXCookiesProvider, consent state, callbacks
src/cookies.js → cc_cookie read/write, gtag, deferred scripts
src/hooks/ → useBreakpoint
src/utils.js/ → translations, classNames helpers
vite.config.js → dev server (`root`: playground/)
vite/vite.config.external.js → production lib → dist/external/
vite/vite.config.standalone.js → production lib → dist/standalone/ (`vxcookies.js`, `vxcookies.cjs`, `vxcookies.standalone.iife.js`)
vite/vite.lib.shared.js → shared CSS-in-JS inject plugin + loadEnvThe web component uses light DOM (no Shadow DOM) so global or imported CSS applies predictably.
Cookie format & backend compatibility
- Cookie name:
cc_cookie - Shape (JSON): includes at least
level(string[] of category ids),consent_date,consent_uuid,last_consent_update. - Lifetime: 182 days,
Path=/,SameSite=Lax,Secureon HTTPS. - Domain: the
Domainattribute is set towindow.location.hostnameonly when the hostname contains a dot and is not an IPv4 address (e.g.localhostand192.168.x.xstay host-only cookies, which avoids brokenDomainvalues on IPs). - Subdomains:
example.comandwww.example.comare different hosts; consent cookies are not automatically shared between them unless your deployment consistently uses one canonical host.
Helpers in src/cookies.js (getConsent, setConsent, hasConsent, allowedCategory) are used internally; the same cookie can be read server-side if naming and JSON stay aligned with your PHP/helper expectations.
Google Analytics / gtag
When consent is applied, the library calls gtagConsentUpdate, which runs:
gtag('consent', 'update', {
ad_storage: granted ? 'granted' : 'denied',
analytics_storage: granted ? 'granted' : 'denied',
});granted is true when the analytics category is in the accepted list. Ensure window.gtag exists if you rely on this.
Deferred scripts (data-cookiecategory)
To load a script only after a category is allowed, use a non-executing placeholder so the browser does not run it before consent (use type="text/plain" or another non-JavaScript MIME type, not text/javascript):
<script
type="text/plain"
data-cookiecategory="analytics"
data-src="https://www.googletagmanager.com/gtag/js?id=G-XXXX"
></script>When the user accepts that category, the library replaces these nodes with real <script type="text/javascript"> elements (similar in spirit to legacy cookieconsent behaviour). Category strings must match CookieCategories values (necessary, functional, analytics).
window.__cookieConsentApi
After the provider mounts, the library assigns:
window.__cookieConsentApi = {
allowedCategory: (category) => boolean,
showSettings: () => void,
};Use this from non-React code to open the settings modal or check permissions. It is removed on unmount.
Styling notes
- Build plugin (
vite/vite.lib.shared.js): extracts the compiled CSS asset and prepends an IIFE that injects it intodocument.headonce (data-vxcookies), so a simple JS import often suffices. - CSS modules use camelCase locals for class names in JS (
localsConvention: 'camelCase'). - Optional separate imports:
@vstx/vxcookies/style.cssor…/standalone/style.css.
Development
git clone https://github.com/Campoint/vxcookies.git
cd vxcookies
npm install
npm run dev # Vite dev server; root = playground/
npm run build # vite/vite.config.* → dist/external/ & dist/standalone/ (+ standalone IIFE)
npm run preview # preview production playground build- Library source entry:
src/index.js - Vite:
npm run devusesvite.config.jswithroot: playground/. Library production builds usevite/vite.config.external.js(peer React),vite/vite.config.standalone.js(bundled React), and sharedvite/vite.lib.shared.js. Optional env:VITE_HOST,VITE_PORT(e.g. in.envat project root).
Playground layout
| Path | Role |
|------|------|
| playground/index.html | Dev server HTML shell; favicon, <div id="root">, and scripts that load vxcookies |
| playground/main.jsx | Recommended Vite entry when enabled in HTML: calls init() then mounts a sample React “host” app |
| playground/App.jsx | Dummy host UI (counter, links) — simulates an app that lives next to the cookie banner |
| playground/main.css, App.css | Playground-only styles |
| playground/assets/ | Images used by init({ platform: { logoUrl } }) (e.g. heidi.png) |
Two ways to run the playground
1. Test the built library (current default in index.html)index.html uses an inline module that imports ../dist/external/vxcookies.js and style.css. You need a successful npm run build first, or the imports 404. Use this to verify production-like bundles without HMR on src/.
2. Develop the library with HMR (recommended for UI/logic work)
Point the HTML at the Vite-processed entry:
- In
playground/index.html, comment out the inline<script type="module">block that imports fromdist/. - Uncomment
<script type="module" src="./main.jsx"></script>.
Then main.jsx loads the library from ../src/index.js (see useReactComponents below) so edits under src/ hot-reload with the dev server.
main.jsx — useReactComponents flag
const useReactComponents = true;
if (useReactComponents) {
vxcookies = (await import('../src/index.js')).default;
} else {
vxcookies = (await import('@vstx/vxcookies')).default;
}useReactComponents === true— uses source (src/index.js): best for changing components, Less, orinitbehaviour.useReactComponents === false— uses the installed npm package@vstx/vxcookies: smoke-test the same code consumers get (afternpm link/ localfile:install).
Adjust init({ ... }) in main.jsx (locale, theme, platform, URLs, callbacks) to match what you want to test. The cookie UI is not rendered inside <div id="root"> — init() appends <vx-cookies> to document.body; App only represents the rest of the page.
After changing styles or library code
- HMR path: save files under
src/orplayground/; Vite refreshes as usual. - Dist path: run
npm run buildagain to refreshdist/externalanddist/standalonebefore testing the inline-distHTML workflow or publishing.
Browser support
Requires APIs used by the bundle: custom elements, JSON/localStorage-free cookie I/O, and modern React 18 client APIs in whichever bundle you load. The default and standalone ESM entries assume the host can load ES modules; the standalone IIFE does not require type="module" in the page. For very old browsers, transpile or polyfill at the application level.
Limitations & caveats
init()is browser-only — guard any SSR imports sodocumentis not accessed on the server, or load the library only on the client.- Single instance — one
<vx-cookies>per page is assumed; duplicateinit()calls are ignored with a console warning. - Multiple
init()beforeDOMContentLoaded— if you callinit()more than once while the document is still loading, each call registers aDOMContentLoadedhandler; the first run mounts<vx-cookies>, and later runs may only log the “already initialized” warning without applying newer options. Callinit()once with the final options (or wait untilDOMContentLoadedyourself). onFirstAcceptnaming — it runs when consent is applied with analytics included, not on every first acceptance of any category and not strictly “first visit only”; usehasConsent()/ cookie inspection for “first visit” logic if needed.- Standalone vs host React — importing
@vstx/vxcookies/standaloneinside a React app loads a second React runtime (larger bundle, avoid unless you mean to). - Vanilla
<script type="module">+/node_modules/...URLs — the browser must be able to HTTP-fetch the module path; many setups do not exposenode_modulesas static files—prefer a bundler, copyvxcookies.jsto a public URL, or use the standalone IIFE with a normal<script src>.
License
MIT © Campoint / VSTX
Links
Made with ❤️ by CMPNT for VSTX / Baukasten.
