Overview
A fixed bottom banner by default, with an optional compact desktop card and a programmatic entry point for re-opening
This website uses cookies to ensure ease of use and provide an enhanced browsing experience. By continuing to browse this site, you acknowledge and accept the use of cookies.
<div class="nds-cookie-popup nds-card" id="ndsCookiesPopup" hidden>
<div class="nds-card-header">
<span class="nds-featured-icon nds-circle">
<i class="nds-icon nds-hgi-cookie" aria-hidden="true"></i>
</span>
<button id="ndsCookiesCloseBtn" class="nds-close nds-btn nds-subtle" aria-label="Close">
<i class="nds-icon nds-hgi-cancel-01" aria-hidden="true"></i>
</button>
</div>
<div class="nds-card-content">
<div class="nds-card-text">
<span class="nds-card-title" id="ndsCookiesTitle">Cookies</span>
<p class="nds-card-description" id="ndsCookiesContent">This website uses cookies to ensure ease of use and provide an enhanced browsing experience. By continuing to browse this site, you acknowledge and accept the use of cookies.</p>
</div>
<div class="nds-cookie-popup-links">
<a href="/terms-and-conditions" id="ndsCookiesTermsLink">Terms & Conditions</a>
<span>|</span>
<a href="/privacy-policy" id="ndsCookiesPrivacyLink">Privacy Policy</a>
</div>
<div class="nds-card-actions">
<button class="nds-btn nds-primary nds-full" id="ndsCookiesAcceptBtn"
data-accept-title="Accepted" data-accept-message="Cookies have been accepted">
<span class="nds-label">Accept</span>
</button>
<button class="nds-btn nds-secondary nds-full" id="ndsCookiesDeclineBtn"
data-decline-title="Non-essential rejected" data-decline-message="Non-essential cookies rejected">
<span class="nds-label">Reject Non-Essential</span>
</button>
</div>
</div>
</div>
Google Analytics Setup
The banner manages consent signals, but you bring your own gtag.js. Default Consent Mode to denied so nothing tracks before the user decides; the banner switches it to granted on Accept and disables it on Reject.
Wire up gtag with consent denied by default
NDS does not load Google Analytics for you, and it sends no consent signal until a choice is stored. Set Consent Mode to denied before gtag.js loads, then register your property ID so the banner can disable it on reject. Place this in the document <head>, before the NDS scripts.
<!-- In <head>, before the NDS scripts and before gtag.js -->
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
// Deny analytics + ads by default, so nothing tracks until the user accepts.
gtag('consent', 'default', { analytics_storage: 'denied', ad_storage: 'denied' });
// Tell NDS which tracking id(s) to disable on "Reject Non-Essential".
window.GA_TRACKING_ID = 'G-XXXXXXXXXX'; // a string, or an array of ids
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
What the banner does after
- On Accept: emits
gtag('consent', 'update', ...)withanalytics_storageandad_storagegranted. - On Reject Non-Essential: sets
window['ga-disable-<id>']for every registered ID, clears the_ga,_gid,_gat,_fbp, and_fbccookies, and emits the same update with values denied. - On the next visit: the saved choice is re-applied on load, so a returning user is not re-prompted and analytics resumes only if they accepted.
- Before any choice: the banner sends no signal, so your
denieddefault stays in force. That default is what keeps the first page view from tracking.
Built-in Features
Opens six seconds after first load when no consent is stored. Re-visits skip the banner until you clear the saved preference.
Saves the choice for 365 days with SameSite=Lax, and falls back to localStorage when the site runs from a file:// URL.
On decline, clears _ga, _gid, _gat, _fbp, _fbc cookies and sets the Google Analytics ga-disable flag for every configured tracking ID.
Emits gtag('consent', 'update', ...) with granted or denied values on every decision, so downstream Google tags react immediately without a page reload.
Adding nds-compact re-centers the banner as a capped-width card on tablet and up, staying full-width on mobile.
Call NDS.Cookies.show() from any trigger (footer link, settings button) to re-open the banner after a decision has been made.
The slideUp entrance animation is suppressed automatically when the user has enabled the prefers-reduced-motion system setting.
Usage Guidelines
Best Practices
- Let the component auto-open on the first visit. A 6000 ms delay keeps the banner from fighting with first-paint content, and the saved consent means return visitors never see it again unnecessarily
- Add a persistent "Cookie Settings" link in the footer that calls
NDS.Cookies.show(). Consent is a reversible decision, and users expect to find the control later without digging - Choose
nds-compactfor desktop-first products where the bottom banner would cover a fixed footer, chatbot, or data table. The compact card sits above content as a floating dialog rather than a bar - Keep the body text to one or two sentences. Lead with the "why", then link to the Terms and Privacy pages for the full policy. Users skim, not read, consent banners
- Always include both Terms & Conditions and Privacy Policy links. Regulations in most jurisdictions require them to be reachable at the moment of consent, not only after. The bundled include points to
/terms-and-conditionsand/privacy-policy; override_includes/cookie-popup.htmlif your site uses different paths - Wire Accept as the primary button and Reject Non-Essential as the secondary. This matches user expectation and keeps keyboard focus order predictable
- Register your Google Analytics property ID through
window.GA_TRACKING_ID(string or array) or adata-ga-tracking-idattribute so Reject Non-Essential can set thega-disable-<ID>flag. Without a registered ID, the reject action still clears the cookies but GA keeps running - Treat the close (×) button as "decide later", not "decline". It hides the banner for the current page load without writing a consent value, so the popup re-appears on the next visit
- Do not use this banner as a blocking gate. The page must remain readable and interactive behind it. For mandatory decisions (age gates, payment confirmations) use the Modal component with a backdrop instead
- Do not stack multiple cookie banners on the same page. The component is already included in the site layout, so you do not need to add it to individual pages
- Custom toast strings are used verbatim when set via the data attributes. Set both Arabic and English variants per page (or leave the attribute off to inherit the language-aware default, which keys on
NDS.isArabic)
Modifier Classes
| Class | Description |
|---|---|
nds-card | Required. The popup composes on Cards for background, padding, radius, and typography. Keep it on the root element |
nds-compact | Re-centers the banner as a capped-width floating card on tablet and larger viewports. Mobile behavior is unchanged (full-width bottom bar). Do not confuse with card's nds-center, which centers card content, not the card itself |
Data Attributes
| Attribute | Description |
|---|---|
data-accept-title | Set on #ndsCookiesAcceptBtn to override the toast title shown after Accept. Falls back to a language-aware default (EN: "Accepted", AR: "تم القبول") |
data-accept-message | Set on #ndsCookiesAcceptBtn to override the toast description after Accept |
data-decline-title | Set on #ndsCookiesDeclineBtn to override the toast title shown after Reject Non-Essential |
data-decline-message | Set on #ndsCookiesDeclineBtn to override the toast description after Reject Non-Essential |
data-ga-tracking-id | Set on any element to register a Google Analytics tracking ID that should be disabled when the user declines. Multiple IDs are supported (one per element) |
Required Element IDs
The JS wires events and updates text by ID, so renaming or removing these on your copy of the markup will break the component. If you translate the banner server-side, edit the text nodes inside these IDs but keep the IDs themselves intact.
| ID | Required on |
|---|---|
#ndsCookiesPopup | Root .nds-cookie-popup container. Target of NDS.Cookies.show() and the hidden attribute toggle |
#ndsCookiesAcceptBtn | Primary Accept button. Also the selector the loader watches to know when to initialize |
#ndsCookiesDeclineBtn | Secondary Reject Non-Essential button |
#ndsCookiesCloseBtn | Close (×) button. Dismisses the popup without persisting a consent value |
JavaScript API
The NDS.Cookies namespace exposes both the consent lifecycle and a generic cookie read/write utility. NDS.Cookies.init() is called automatically by the loader, so most integrations only need show, getConsent, and the cookie helpers. Consent is persisted in a cookie named cookieConsent for 365 days (or in localStorage under nds_cookieConsent when the site is served from file://).
// ── Re-open the banner ───────────────────────────────
// Removes the `hidden` attribute from #ndsCookiesPopup.
// Works at any time, regardless of whether consent was
// previously saved. Useful for a footer "Cookie Settings"
// link or an in-app preferences screen.
NDS.Cookies.show();
// ── Read the current consent state ───────────────────
// Returns 'accepted' | 'declined' | null (no decision yet).
const state = NDS.Cookies.getConsent();
// ── Reset consent and re-prompt ──────────────────────
// There is no dedicated reset method; clear the cookie
// and call show() to re-ask the user.
NDS.Cookies.delete('cookieConsent');
NDS.Cookies.show();
// ── Set a functional cookie ──────────────────────────
// Essential cookies do not require consent. Use this for
// user-preference flags, dismissed banners, etc.
// Cookies are written with SameSite=Lax and path=/.
NDS.Cookies.set('my-preference', 'value', 30); // name, value, days
// ── Read a cookie by name ────────────────────────────
const value = NDS.Cookies.get('my-preference');
// ── Delete a cookie ──────────────────────────────────
// Clears both the root-domain and host-only variants.
NDS.Cookies.delete('my-preference');
// ── Register a Google Analytics property ID ──────────
// Set before nds-main.min.js loads (e.g. in <head>).
// String or array; Reject Non-Essential sets window['ga-disable-<ID>'] = true
// for every registered ID and emits gtag('consent', 'update', …).
window.GA_TRACKING_ID = 'G-XXXXXXXXXX';
// or
window.GA_TRACKING_ID = ['G-AAA', 'G-BBB'];
// ── Re-initialize after dynamic markup (rare) ────────
// Only needed if the banner markup was injected after load.
// Re-binds click handlers to the four required IDs.
NDS.Cookies.init();