// Open a dialog from any button with data-open="dialog-id". showModal() traps focus, closes on Esc
// and makes the page behind inert. Buttons inside <form method="dialog"> close it with no script.
document.querySelectorAll('[data-open]').forEach(function (btn) {
var dialog = document.getElementById(btn.getAttribute('data-open'));
if (!dialog) return;
btn.addEventListener('click', function () {
dialog.showModal();
dialog.addEventListener('close', function () { btn.focus(); }, { once: true }); // focus back to the trigger
});
// A click on the backdrop lands on the <dialog> itself (its form fills the box), so close it
dialog.addEventListener('click', function (e) { if (e.target === dialog) dialog.close(); });
});
// Command palette: Ctrl+K / Cmd+K opens it from anywhere, typing filters the results,
// ArrowUp/ArrowDown move between visible results and Enter in the search box opens the first one.
(function () {
var pal = document.getElementById('dk-palette');
if (!pal) return;
var input = document.getElementById('dk-palette-q');
var empty = document.getElementById('dk-palette-empty');
var count = document.getElementById('dk-palette-count');
var items = [].slice.call(pal.querySelectorAll('[data-dk-item]'));
var groups = [].slice.call(pal.querySelectorAll('[data-dk-group]'));
function shown() { return items.filter(function (el) { return !el.hidden; }); }
function filter() {
var q = input.value.trim().toLowerCase();
items.forEach(function (el) { el.hidden = el.getAttribute('data-dk-item').toLowerCase().indexOf(q) === -1; });
groups.forEach(function (g) { g.hidden = !g.querySelector('[data-dk-item]:not([hidden])'); });
var n = shown().length;
empty.hidden = n > 0;
empty.querySelector('[data-dk-query]').textContent = input.value.trim();
count.textContent = !q ? '' : n ? n + (n === 1 ? ' result' : ' results') : 'No results'; // read out by aria-live
}
input.addEventListener('input', filter);
pal.addEventListener('close', function () { input.value = ''; filter(); }); // start fresh next time
pal.addEventListener('keydown', function (e) {
var list = shown(), i = list.indexOf(document.activeElement);
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
e.preventDefault();
if (!list.length) return;
if (e.key === 'ArrowDown') (list[i + 1] || list[0]).focus();
else if (i <= 0) input.focus();
else list[i - 1].focus();
} else if (e.key === 'Enter' && e.target === input) {
e.preventDefault(); // do not submit the form; open the top result instead
if (list[0]) list[0].click();
} else if (i > -1 && e.key.length === 1 && e.key !== ' ' && !e.ctrlKey && !e.metaKey && !e.altKey) {
input.focus(); // typing on a result goes back to the search box
}
});
// Links do not close a dialog by themselves
items.forEach(function (el) { if (el.tagName === 'A') el.addEventListener('click', function () { pal.close(); }); });
document.addEventListener('keydown', function (e) {
if (!(e.ctrlKey || e.metaKey) || e.key.toLowerCase() !== 'k') return;
e.preventDefault();
if (pal.open) { input.focus(); return; }
if (document.querySelector('dialog:modal')) return; // another dialog is already open
var back = document.activeElement;
pal.showModal();
pal.addEventListener('close', function () { if (back && back.focus) back.focus(); }, { once: true });
});
})();
// Bootstrap ignores autofocus inside modals: honour it (the Cancel button of the confirmation)
document.querySelectorAll('.modal').forEach(function (m) {
m.addEventListener('shown.bs.modal', function () { var a = m.querySelector('[autofocus]'); if (a) a.focus(); });
});
// Command palette: Ctrl+K / Cmd+K opens it from anywhere, typing filters the results,
// ArrowUp/ArrowDown move between visible results and Enter in the search box opens the first one.
(function () {
var pal = document.getElementById('dk-palette');
if (!pal || !window.bootstrap) return;
var modal = bootstrap.Modal.getOrCreateInstance(pal);
var input = document.getElementById('dk-palette-q');
var empty = document.getElementById('dk-palette-empty');
var count = document.getElementById('dk-palette-count');
var items = [].slice.call(pal.querySelectorAll('[data-dk-item]'));
var groups = [].slice.call(pal.querySelectorAll('[data-dk-group]'));
var back = null;
function shown() { return items.filter(function (el) { return !el.hidden; }); }
function filter() {
var q = input.value.trim().toLowerCase();
items.forEach(function (el) { el.hidden = el.getAttribute('data-dk-item').toLowerCase().indexOf(q) === -1; });
groups.forEach(function (g) { g.hidden = !g.querySelector('[data-dk-item]:not([hidden])'); });
var n = shown().length;
empty.hidden = n > 0;
empty.querySelector('[data-dk-query]').textContent = input.value.trim();
count.textContent = !q ? '' : n ? n + (n === 1 ? ' result' : ' results') : 'No results'; // read out by aria-live
}
input.addEventListener('input', filter);
pal.addEventListener('show.bs.modal', function () { back = document.activeElement; });
pal.addEventListener('shown.bs.modal', function () { input.focus(); });
pal.addEventListener('hidden.bs.modal', function () {
input.value = ''; filter(); // start fresh next time
if (back && back !== document.body && back.focus) back.focus(); // also covers opening with Ctrl+K
});
pal.addEventListener('keydown', function (e) {
var list = shown(), i = list.indexOf(document.activeElement);
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
e.preventDefault();
if (!list.length) return;
if (e.key === 'ArrowDown') (list[i + 1] || list[0]).focus();
else if (i <= 0) input.focus();
else list[i - 1].focus();
} else if (e.key === 'Enter' && e.target === input) {
e.preventDefault();
if (list[0]) list[0].click(); // open the top result
} else if (i > -1 && e.key.length === 1 && e.key !== ' ' && !e.ctrlKey && !e.metaKey && !e.altKey) {
input.focus(); // typing on a result goes back to the search box
}
});
// Links close the palette without data-bs-dismiss, which would cancel their navigation
items.forEach(function (el) { if (el.tagName === 'A') el.addEventListener('click', function () { modal.hide(); }); });
document.addEventListener('keydown', function (e) {
if (!(e.ctrlKey || e.metaKey) || e.key.toLowerCase() !== 'k') return;
e.preventDefault();
if (pal.classList.contains('show')) { input.focus(); return; }
if (document.querySelector('.modal.show')) return; // another modal is already open
modal.show();
});
})();
/* Entrance keyframes for the dialogs and their backdrop (used through motion-safe:open:animate-[…]) */
@keyframes dk-in { from { opacity: 0; transform: translateY(8px) scale(.98); } }
@keyframes dk-fade { from { opacity: 0; } }
/* Stop the page scrolling behind an open modal */
html:has(dialog:modal) { overflow: hidden; }
JavaScript
// Open a dialog from any button with data-open="dialog-id". showModal() traps focus, closes on Esc
// and makes the page behind inert. Buttons inside <form method="dialog"> close it with no script.
document.querySelectorAll('[data-open]').forEach(function (btn) {
var dialog = document.getElementById(btn.getAttribute('data-open'));
if (!dialog) return;
btn.addEventListener('click', function () {
dialog.showModal();
dialog.addEventListener('close', function () { btn.focus(); }, { once: true }); // focus back to the trigger
});
// A click on the backdrop lands on the <dialog> itself (its form fills the box), so close it
dialog.addEventListener('click', function (e) { if (e.target === dialog) dialog.close(); });
});
// Command palette: Ctrl+K / Cmd+K opens it from anywhere, typing filters the results,
// ArrowUp/ArrowDown move between visible results and Enter in the search box opens the first one.
(function () {
var pal = document.getElementById('dk-palette');
if (!pal) return;
var input = document.getElementById('dk-palette-q');
var empty = document.getElementById('dk-palette-empty');
var count = document.getElementById('dk-palette-count');
var items = [].slice.call(pal.querySelectorAll('[data-dk-item]'));
var groups = [].slice.call(pal.querySelectorAll('[data-dk-group]'));
function shown() { return items.filter(function (el) { return !el.hidden; }); }
function filter() {
var q = input.value.trim().toLowerCase();
items.forEach(function (el) { el.hidden = el.getAttribute('data-dk-item').toLowerCase().indexOf(q) === -1; });
groups.forEach(function (g) { g.hidden = !g.querySelector('[data-dk-item]:not([hidden])'); });
var n = shown().length;
empty.hidden = n > 0;
empty.querySelector('[data-dk-query]').textContent = input.value.trim();
count.textContent = !q ? '' : n ? n + (n === 1 ? ' result' : ' results') : 'No results'; // read out by aria-live
}
input.addEventListener('input', filter);
pal.addEventListener('close', function () { input.value = ''; filter(); }); // start fresh next time
pal.addEventListener('keydown', function (e) {
var list = shown(), i = list.indexOf(document.activeElement);
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
e.preventDefault();
if (!list.length) return;
if (e.key === 'ArrowDown') (list[i + 1] || list[0]).focus();
else if (i <= 0) input.focus();
else list[i - 1].focus();
} else if (e.key === 'Enter' && e.target === input) {
e.preventDefault(); // do not submit the form; open the top result instead
if (list[0]) list[0].click();
} else if (i > -1 && e.key.length === 1 && e.key !== ' ' && !e.ctrlKey && !e.metaKey && !e.altKey) {
input.focus(); // typing on a result goes back to the search box
}
});
// Links do not close a dialog by themselves
items.forEach(function (el) { if (el.tagName === 'A') el.addEventListener('click', function () { pal.close(); }); });
document.addEventListener('keydown', function (e) {
if (!(e.ctrlKey || e.metaKey) || e.key.toLowerCase() !== 'k') return;
e.preventDefault();
if (pal.open) { input.focus(); return; }
if (document.querySelector('dialog:modal')) return; // another dialog is already open
var back = document.activeElement;
pal.showModal();
pal.addEventListener('close', function () { if (back && back.focus) back.focus(); }, { once: true });
});
})();
About this set
Three dark-theme dialogs for developer tools and SaaS dashboards: near-black panels with a hairline border, a soft top highlight, a cyan focus ring and a violet accent over a blurred backdrop. The Command palette opens from its trigger or with Ctrl+K or Cmd+K anywhere on the page and sits near the top of the screen. Typing filters grouped results (suggestions, projects, help), hides empty groups, shows a no-results message and announces the result count. The arrow keys move between results, and Enter opens the top result. Revoke API key is a destructive confirmation: an alertdialog with a red-glow icon, the masked key in monospace, created and last-used dates, a warning that two services depend on it, and Cancel focused first. Upgrade plan is a pricing card with a violet-to-cyan gradient border. Its monthly and yearly toggle uses real radio inputs and :has() to switch the price with no script, and a feature list and full-width call to action follow. The plain and Tailwind versions use the native dialog element and showModal(); the Bootstrap version uses the Modal plugin in its dark colour mode. The only script beyond the opener is the palette’s filtering and shortcut code.
What’s included
Command palette with Ctrl+K / Cmd+K, live filtering, grouped results and arrow-key navigation
Destructive alertdialog with a masked API key, usage details and a dependency warning
Pricing card with a gradient border and a CSS-only monthly/yearly price toggle
Near-black panels with hairline borders, a blurred backdrop and text contrast of 4.5:1 or better
Bootstrap version in Bootstrap 5.3's dark colour mode (data-bs-theme="dark")
Accessibility
Each dialog is named by its heading (a visually hidden one for the palette), the revoke confirmation is a role="alertdialog" described by its warning line, and the search box has a real label. Filtering updates a visually hidden aria-live count, the arrow keys move focus between visible results, the billing toggle is a fieldset of real radio inputs with a legend, and every control shows a cyan focus ring. Focus returns to the trigger, or to the element in use when Ctrl+K was pressed, and entrance animations are switched off under prefers-reduced-motion.
Customise it
In the plain version change --dk-bg, --dk-line, --dk-ink, --dk-muted, --dk-ring and --dk-w on .dk, and the gradient on .dk.is-plan. In Bootstrap change --bs-modal-bg, --bs-modal-border-color, --bs-modal-width and the --bs-btn-* and --bs-list-group-* variables. In Tailwind change the bg-[#12151c], border-[#262b37] and outline-[#22d3ee] classes.