Components/Overlays
A modal moment. Focus is trapped inside; escape or a click outside closes it.
Default
Props
Installation
Paste the source into components/dialog.tsx. Traps focus and closes on escape or backdrop click — no dependencies required.
'use client';
import { useEffect, useRef } from 'react';
import type { ReactNode } from 'react';
interface DialogProps {
open: boolean;
onClose: () => void;
title: string;
children?: ReactNode;
}
export function Dialog({ open, onClose, title, children }: DialogProps) {
const panelRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!open) return;
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
return;
}
if (e.key === 'Tab') {
const panel = panelRef.current;
if (!panel) return;
const focusable = panel.querySelectorAll<HTMLElement>(
'a[href], button, textarea, input, select, [tabindex]:not([tabindex="-1"])'
);
if (focusable.length === 0) return;
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
};
document.addEventListener('keydown', onKeyDown);
panelRef.current?.focus();
return () => document.removeEventListener('keydown', onKeyDown);
}, [open, onClose]);
if (!open) return null;
return (
<div
onClick={onClose}
style={{
position: 'fixed',
inset: 0,
backgroundColor: 'rgba(26, 24, 20, 0.32)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 100,
padding: '24px',
}}
>
<div
ref={panelRef}
role="dialog"
aria-modal="true"
aria-label={title}
tabIndex={-1}
onClick={(e) => e.stopPropagation()}
style={{
width: '100%',
maxWidth: '420px',
backgroundColor: 'var(--color-surface)',
border: '0.5px solid var(--color-border)',
borderRadius: 'var(--radius-lg)',
padding: '24px',
outline: 'none',
}}
>
<h2
style={{
margin: '0 0 8px',
fontFamily: 'var(--font-sans)',
fontSize: '18px',
fontWeight: 540,
letterSpacing: '-0.01em',
color: 'var(--color-text)',
}}
>
{title}
</h2>
{children && (
<div
style={{
fontFamily: 'var(--font-sans)',
fontSize: '14px',
fontWeight: 420,
color: 'var(--color-text-muted)',
lineHeight: 1.6,
}}
>
{children}
</div>
)}
</div>
</div>
);
}Built from Lumen tokens. →Edit the tokens