---
title: Headless Bar
lastModified: "2026-10-07T18:56:41+01:00"
lastAuthor: Kaylee
---
```html title="index.html"
<script
	src="https://your-project.inth.app/c15t.headless.js"
	defer
></script>

<style>
	.cookie-bar {
		position: fixed;
		inset: auto 0 0;
		z-index: 50;
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: 0.75rem 1.5rem;
		padding: 0.875rem 1.5rem;
		background: #14213d;
		color: #f8f9fb;
		font:
			0.9375rem/1.5 system-ui,
			sans-serif;
		box-shadow: 0 -4px 16px rgb(0 0 0 / 0.15);
	}

	.cookie-bar__text {
		flex: 1 1 20rem;
		margin: 0;
	}

	.cookie-bar__actions {
		display: flex;
		flex-wrap: wrap;
		gap: 0.5rem;
	}

	.cookie-bar button {
		padding: 0.5rem 1rem;
		border: 1px solid #f8f9fb;
		border-radius: 999px;
		background: transparent;
		color: inherit;
		font: inherit;
		cursor: pointer;
	}

	.cookie-bar .cookie-bar__button {
		background: #f8f9fb;
		color: #14213d;
		font-weight: 600;
	}

	.cookie-bar .cookie-bar__link {
		border-color: transparent;
		text-decoration: underline;
		text-underline-offset: 3px;
	}

	.cookie-bar button:focus-visible {
		outline: 2px solid #fca311;
		outline-offset: 2px;
	}
	/* On narrow screens, Reject and Accept share a row at equal width. */
	@media (max-width: 30rem) {
		.cookie-bar__actions {
			width: 100%;
		}

		.cookie-bar__actions button {
			flex: 1 1 40%;
		}

		.cookie-bar__actions .cookie-bar__link {
			flex-basis: 100%;
		}
	}

	/* The headless build has no dialog, so preferences open inside the bar. */
	.cookie-bar[hidden],
	.cookie-bar__prefs[hidden] {
		display: none;
	}

	.cookie-bar__prefs {
		display: flex;
		flex-wrap: wrap;
		gap: 0.5rem 1.25rem;
		flex-basis: 100%;
		margin: 0;
		padding: 0;
		border: 0;
	}

	.cookie-bar__prefs label {
		display: flex;
		align-items: center;
		gap: 0.375rem;
	}
</style>

<section
	id="cookie-bar"
	class="cookie-bar"
	aria-label="Cookie consent"
	hidden
>
	<p class="cookie-bar__text">
		We use cookies to measure traffic and improve this site. Choose which
		ones can run.
	</p>
	<fieldset
		class="cookie-bar__prefs"
		hidden
	>
		<legend class="cookie-bar__text">Allow these cookies:</legend>
		<label
			><input
				type="checkbox"
				name="functionality"
			/>
			Functionality</label
		>
		<label
			><input
				type="checkbox"
				name="measurement"
			/>
			Measurement</label
		>
		<label
			><input
				type="checkbox"
				name="experience"
			/>
			Experience</label
		>
		<label
			><input
				type="checkbox"
				name="marketing"
			/>
			Marketing</label
		>
	</fieldset>
	<div class="cookie-bar__actions">
		<!-- c15t wires data-c15t-action buttons itself; they need no code. -->
		<button
			type="button"
			class="cookie-bar__link"
			data-c15t-action="customize"
		>
			Preferences
		</button>
		<button
			type="button"
			class="cookie-bar__button"
			data-c15t-action="dismiss"
			data-notice-only
			hidden
		>
			Got it
		</button>
		<button
			type="button"
			class="cookie-bar__button"
			data-c15t-action="reject"
			data-choice-only
		>
			Reject all
		</button>
		<button
			type="button"
			class="cookie-bar__button"
			data-c15t-action="accept"
			data-choice-only
		>
			Accept all
		</button>
		<button
			type="button"
			class="cookie-bar__button"
			data-save
			hidden
		>
			Save
		</button>
	</div>
</section>

<script>
	const bar = document.getElementById('cookie-bar');
	const prefs = bar.querySelector('.cookie-bar__prefs');
	const saveButton = bar.querySelector('[data-save]');
	const switches = prefs.querySelectorAll('input');
	const choiceButtons = bar.querySelectorAll('[data-choice-only]');
	const noticeButtons = bar.querySelectorAll('[data-notice-only]');

	// The tag is deferred, so queue calls until window.c15t is the real API.
	window.c15t = window.c15t || [];
	const queue = (call) => {
		if (Array.isArray(window.c15t)) {
			window.c15t.push(call);
		} else {
			window.c15t[call[0]](...call.slice(1));
		}
	};

	// c15t says which surface is due; the page decides what it looks like.
	// 'banner' shows the bar, 'dialog' opens the switches, 'none' hides it.
	queue([
		'on',
		'ui',
		(surface) => {
			const { kind } = c15t.getSnapshot().promptRequirement;
			const editing = surface === 'dialog';
			// A notice gets one acknowledgement button instead of Accept and Reject.
			for (const button of choiceButtons) {
				button.hidden = kind === 'notice' && !editing;
			}
			for (const button of noticeButtons) {
				button.hidden = kind !== 'notice' || editing;
			}
			for (const input of switches) {
				input.checked = c15t.has(input.name);
			}
			prefs.hidden = !editing;
			saveButton.hidden = !editing;
			bar.hidden =
				surface === 'none' || (surface === 'banner' && kind === 'none');
		},
	]);

	// Save records exactly what the switches show.
	saveButton.addEventListener('click', () => {
		const choice = {};
		for (const input of switches) {
			choice[input.name] = input.checked;
		}
		c15t.save(choice);
	});
</script>
```
