# Creating a Custom Consent UI (/docs/consent-management/configuration/consent-ui/creating-your-own-ui)

## Overview [#overview]

Transcend's Consent Manager is split up into a handful of files:

- `airgap.js`: This is the core CMP code that is necessary to regulate cookies and data flows based on the output of `airgap.getConsent()`
- `xdi.js`: This file is responsible for the code that is used to synchronize consent preferences across subdomains.
- `ui.js`: This is a lazy-loaded discrete JavaScript file that is capable of rendering the interface(s) necessary to collect consent. We provide an [open source reference UI package](https://github.com/transcend-io/consent-manager-ui) that can be extended however you see fit. This package provides a strong foundation with full regime-specific UI integration, compatibility with our auto-prompt functionality, and translations for 54 languages.
- `cm.css`: This is the stylesheet that is loaded by the default consent manager UI `ui.js`
- `translations/*.json`: These are a series of files that contain the translations used in the open source consent manager `ui.js` file.
  - e.g. `translations/en.json`
  - e.g. `translations/es-ES.json`
  - See the full list of locales [here](https://github.com/transcend-io/consent-manager-ui/tree/main/src/translations)

When deciding to build a custom consent interface, you have the following options that get increasingly more custom:

### Option 1) Override stylesheet and messages in Transcend Dashboard using out-of-the-box UI [#option-1-override-stylesheet-and-messages-in-transcend-dashboard-using-out-of-the-box-ui]

The simplest way to customize a consent interface is to override the `cm.css` stylesheet or `translations/*.json` files. This can be done by modifying your [Display Settings](https://app.transcend.io/consent-manager/display-settings) in the Transcend Dashboard. When you customize your stylesheet or messages and publish those changes live, the `cm.css` and `translations/*.json` files will be updated. These files sit in the same folder as your `airgap.js` file, e.g. `https://transcend-cdn.com/cm/6efb0409-a0f6-4d3c-90dd-0a03f909dd68/airgap.js` and `https://transcend-cdn.com/cm/6efb0409-a0f6-4d3c-90dd-0a03f909dd68/cm.css` and `https://transcend-cdn.com/cm/6efb0409-a0f6-4d3c-90dd-0a03f909dd68/translations/en.json`. These files are consumed by the default `ui.js` file or any other custom interface that you decide to develop.

<Callout type="info">
  See [this doc](./configuring-the-ui.mdx) to learn more about customizing the stylesheet (CSS) for our out-of-the-box UIs rather than building an entirely custom UI.
</Callout>

### Option 2) Override stylesheet and/or messages from codebase using out-of-the-box UI [#option-2-override-stylesheet-andor-messages-from-codebase-using-out-of-the-box-ui]

In some situations, you may want to override certain banner messages and styles for specific websites. Instead of managing the stylesheet and messages from the [Transcend Dashboard](https://app.transcend.io/consent-manager/display-settings), you could instead host these files on your own CDN and point your airgap script tag to load the stylesheet and messages from your custom location.

To begin you would

1\. Copy the [folder of translations](https://github.com/transcend-io/consent-manager-ui/tree/main/src/translations) onto your own CDN. Updating the message as desired.
2\. Copy the [default stylesheet](https://github.com/transcend-io/consent-manager-ui/blob/main/src/cm.css) onto your own CDN. Updating the stylesheet as desired.
3\. Pass the location of your stylesheet and translation files to the airgap.js script tag.

```html
<script
  data-cfasync="false"
  src="https://transcend-cdn.com/cm/6efb0409-a0f6-4d3c-90dd-0a03f909dd68/airgap.js"
  data-css="https://my.cdn/cm.css"
  data-translations="https://my.cdn/translations"
></script>
```

An example of this can also be found in the [index.html file of the consent-manager-ui GitHub repo](https://github.com/transcend-io/consent-manager-ui/blob/main/index.html).

This approach can be useful if you dynamically want to change the stylesheet or messages.

### Option 3) Cloning the out-of-the-box UI to create a standalone JavaScript custom UI [#option-3-cloning-the-out-of-the-box-ui-to-create-a-standalone-javascript-custom-ui]

In some situations, it may makes sense to clone our [open source reference UI package](https://github.com/transcend-io/consent-manager-ui) to build a fully custom UI. This is a great solution as it will allow you to have a solid foundation for building a consent UI that is regime-specific, fully compatibile with our auto-prompt functionality in your, and able to load a customizable stylesheet and translations from the Transcend dashboard. This means you can continue to control which banners are displayed for different locations from your [Regional Experiences](https://app.transcend.io/consent-manager/regional-experiences) section of the dashboard, as well as customizing any language or stylesheets from the [Display Settings](https://app.transcend.io/consent-manager/display-settings). This is often beneficial to allow your privacy or legal team to make change without requiring an engineering ticket. In addition, by implementing the standard Transcend interfaces and cloning the UI and bundling into a custom JavaScript file, you will be able to easily distribute your custom UI to all of your websites as that UI code is not tightly coupled to the website it renders on.

To begin you will first want to clone the git repository

```shellscript
git clone https://github.com/transcend-io/consent-manager-ui
```

This will copy the code to a local folder `consent-manager-ui`. You could then incorporate this into an existing git repository, or fork the `consent-manager-ui` repository to create a new repository for this project.

Next, you will want to decide which view states you will want to override. Start by [reviewing the existing view states](https://github.com/transcend-io/consent-manager-ui?tab=readme-ov-file#view-states) and finding the set of interface(s) that most closely align with the layout that you wish to achive.

Once you determine the view state that you want to override, you will want to open up the [src/components](https://github.com/transcend-io/consent-manager-ui/tree/main/src/components) folder to find the file that maps to your view state.

To start development, you will want to install yarn and node. Many versions of yarn and node may work, but if you are stuck, the following instructions can guide you:

- Install Node v18.14.1
  - Install nvm [using these docs](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
  - Run: `nvm install 18.14.1`
  - Run: `nvm use 18.14.1`
  - Run `nvm alias default 18.14.1`
  - Run `node –version`, this should output v18.14.1
- Install yarn v3 or v4
  - Run: `npm install --global yarn`
  - Run: `cd ./consent-manager-ui`
  - Note you may need to change directory to wherever you cloned the consent-manager-ui repo
  - Run: `yarn --version`, it should output 4.5.1
  - If you see a version that is yarn v1, you likely have a version of yarn higher up in the path on your computer. Look for a file called .yarnrc, its often in your home directory e.g. \~/.yarnrc - if you delete this file and re-run yarn --version it should work

Once yarn and node are installed, you can run the following to install dependency, build the application, and start an auto-reloading sessions

```shellscript
yarn install
yarn build
yarn start
```

Once you run `yarn start` this will open the consent UI on `localhost` and you will be able to modify the files within the `src/` folder as needed and the localhost session will auto-reload.

Once you've completed your changes and are ready to bundle your code, you can start the `yarn start` session and instead run:

```shellscript
yarn build
```

This will compile any changes that you have made in the `src/*.ts` files into the `build/` folder. The majority of your JavaScript/TypeScript changes will be in `build/ui.js` but if you decided to modify the `cm.css` and `translations/*.json` files locally - these will also be copied to the build folder.

Once compiled, you can then take that `ui.js` file, host it on your CDN in a public location, and pass it to your airgap.js file using the `data-ui` script tag:

```html
<script
  data-cfasync="false"
  src="https://transcend-cdn.com/cm/6efb0409-a0f6-4d3c-90dd-0a03f909dd68/airgap.js"
  data-ui="https://my.cdn/ui.js"
></script>
```

If desired, you can reach out to Transcend support to ask them to configure a default value for your `data-ui` parameter such that it always points to your self hosted UI by default. This is useful when you have many different websites loading the airgap.js script and you do not want to update all of them to point to your self-hosted UI file.

You may want to setup a continuous build process to publish your changes to a CDN whenever a pull request is merged. Here is an example GitHub action that can be used to get started:

```yaml
name: ci
on: push

jobs:
  build-and-upload-artifacts:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      # 'Install yarn v2 dependencies'
      - uses: actions/setup-node@v3.1.1
        with:
          node-version: '20.18.0'
      - run: corepack enable
        shell: bash
      - uses: nick-fields/retry@v2
        with:
          timeout_minutes: 6
          max_attempts: 3
          retry_on: error
          command: yarn install --immutable

      - name: 'Run tsc'
        run: yarn tsc:build
      - name: Build the typescript code
        run: yarn build
      # <insert logic to push to your CDN>
```

### Option 4) Build a UI from scratch [#option-4-build-a-ui-from-scratch]

Ultimately, you could build your consent UI natively into the website that you are integrating. The benefit of this would be that you could re-use existing components and create a seamless experience for your users (e.g. seeing consent values natively in the settings page). This brings the ultimate freedom of integration, but has the downside of requiring most engineering maintainence, and also not being portable to multiple websites loading your airgap.js script. This option works best when you only care to override the UI for the one specific site that you are looking to customize.

In this situation, you could completely disable the Transcend UI using `data-ui="off"`:

```html
<script
  data-cfasync="false"
  src="https://transcend-cdn.com/cm/6efb0409-a0f6-4d3c-90dd-0a03f909dd68/airgap.js"
  data-ui="off"
></script>
```

#### Asynchronous airgap.js core init [#asynchronous-airgapjs-core-init]

If your UI setup code runs before airgap.js core is initialized, you will need to use `airgap.ready()` as demonstrated below:

```javascript
// Stub airgap.js ready queue
if (!self?.airgap?.ready) {
  self.airgap = {
    readyQueue: [],
    ready(callback) {
      this.readyQueue.push(callback);
    },
    ...self?.airgap,
  };
}

// Wait for airgap.js core to load
self.airgap.ready((airgap) => {
  // Init your UI DOM here
});
```

## Consent authorization [#consent-authorization]

Every airgap.js API that can mutate consent state requires a genuine user gesture (`event.isTrusted == true`) which can be either any `UIEvent` (e.g. `click`) or a form `submit` event.

Script `load` events that fired prior to airgap.js initialization can also be used as valid consent authorization.

```html
<script
  data-cfasync="false"
  src="https://transcend-cdn.com/cm/443312ef-12f9-494d-85f5-969894260cc7/airgap.js"
  onload="setAirgapAuth(event);"
></script>
```

You should pass the load event value to your own trusted code for safekeeping in `setAirgapAuth(event)`.
That event can later be used when authorization is required, like for `airgap.getConsent()` (see below)

```javascript
airgap.setConsent(getAirgapAuth(), consent);
```

### airgap.getConsent() [#airgapgetconsent]

`airgap.getConsent()` returns an `TrackingConsentDetails` descriptor. This descriptor has a `purposes` field which returns an object keyed by all consentable tracking purpose types. The descriptor also has an ISO 8601 timestamp accessible via the `timestamp` field.

**getConsent() Example**

```javascript
await airgap.sync();
const consent = airgap.getConsent().purposes;
console.log(JSON.stringify(consent, null, 2));
```

`airgap.getConsent().purposes` output:

```json
{
  "Functional": false,
  "Analytics": false,
  "Advertising": false
}
```

### airgap.setConsent() [#airgapsetconsent]

`airgap.setConsent()` sets user consent for individual tracking purposes. A trusted interaction Event (or the script load event for the airgap.js script) is required by this API.

**Example**

```javascript
saveConsentButton.addEventListener('click', (interaction) => {
  airgap.setConsent(interaction, serializeConsent());
});
```

<Callout type="info">
  ℹ️ The first argument to `airgap.setConsent` must be a native `click` or `submit` [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event). If you're using React, your event may be a [SyntheticEvent](https://reactjs.org/docs/events.html). In this case, you will need to pass `interaction.nativeEvent`.
</Callout>

### **Simple opt-in/opt-out consent** [#simple-opt-inopt-out-consent]

```javascript
await airgap.sync();

const hasPrivacyRights = airgap.getRegimePurposes().size !== 0;

// Is user fully opted-in to core unessential tracking purposes:
// Functional, Analytics, and Advertising
const isOptedIn = airgap.isOptedIn();

// a similar utility is available for detecting full opt-out
// of these purposes:
// const isOptedOut = airgap.isOptedOut();

const consentForm = document.getElementById('...');
const consentCheckbox = document.getElementById('...');
consentCheckbox.checked = isOptedIn;

if (hasPrivacyRights) {
  consentCheckbox.addEventListener('change', (interaction) => {
    if (interaction.target.checked) {
      // Consent to all user-configurable tracking purposes
      airgap.optIn({ interaction });
    } else {
      // Opt-out of all user-configurable tracking purposes
      airgap.optOut({ interaction });
    }
  });
} else {
  consentForm.style.display = 'none';
  consentCheckbox.disabled = true;
}
```

### Detailed consent [#detailed-consent]

Consent can be set on a per-purpose level using `airgap.getConsent()`, `airgap.setConsent()`, and `airgap.getPurposesTypes()`.

### airgap.getPurposesTypes() [#airgapgetpurposestypes]

`airgap.getPurposesTypes()` returns airgap's current `TrackingPurposesTypes` config. This config data can be used in conjunction with `airgap.getConsent()` to inform the content of a custom consent UI.

### getPurposesTypes() Example [#getpurposestypesexample]

```javascript
const purposes = airgap.getPurposesTypes();
console.log(JSON.stringify(purposes, null, 2));
```

`airgap.getPurposesTypes()` example output:

```json
{
  "Functional": {
    "name": "Functionality",
    "description": "Personalization, autofilled forms, etc.",
    "defaultConsent": false,
    "showInConsentManager": true,
    "configurable": true,
    "essential": false
  },
  "Analytics": {
    "name": "Analytics + Performance",
    "description": "Help us learn how our site is used and how it performs.",
    "defaultConsent": false,
    "showInConsentManager": true,
    "configurable": true,
    "essential": false
  },
  "Advertising": {
    "name": "Targeting / Advertising",
    "description": "Helps us and others serve ads relevant to you.",
    "defaultConsent": false,
    "showInConsentManager": true,
    "configurable": true,
    "essential": false
  },
  "Essential": {
    "name": "Essential",
    "description": "",
    "defaultConsent": true,
    "showInConsentManager": false,
    "configurable": false,
    "essential": true
  },
  "Unknown": {
    "name": "Unknown",
    "description": "",
    "defaultConsent": false,
    "showInConsentManager": false,
    "configurable": false,
    "essential": false
  }
}
```

<Callout type="info">
  ℹ️ You can find additional useful airgap command in our [debugging and testing guide](../../testing-and-troubleshooting/debugging-and-testing.mdx).
</Callout>
