Files
basecoat/src/css/components/dialog.css
T
Grugandgrugbraindev a834708080 Lock page scroll while a modal dialog is open (#214)
Pure CSS: html:has(<dialog>:modal) gets overflow: hidden. A scroll
timeline sets scrollbar-gutter: stable only when the page can scroll,
so the layout doesn't shift.

Co-authored-by: grugbraindev <178141691+grugbraindev@users.noreply.github.com>
2026-10-23 14:52:09 +08:00

116 lines
3.0 KiB
CSS

/* Dialog */
@keyframes dialog-scroll-lock {
from, to {
scrollbar-gutter: stable;
}
}
@layer components {
/* Lock page scroll while a modal dialog is open. The scroll timeline only
reserves the scrollbar gutter if the page can scroll, so nothing shifts. */
html:has(:is(.dialog, .alert-dialog, .command-dialog, .drawer):modal) {
overflow: hidden;
animation: dialog-scroll-lock linear both;
animation-timeline: scroll(self);
}
.dialog,
.alert-dialog {
@apply bg-transparent p-0 text-inherit opacity-0 transition-all transition-discrete backdrop:opacity-0 backdrop:transition-all backdrop:transition-discrete;
max-width: none;
max-height: none;
&:is([open], :popover-open) {
@apply opacity-100 backdrop:opacity-100;
> * {
@apply scale-100;
}
@starting-style {
@apply opacity-0 backdrop:opacity-0;
> * {
@apply scale-95;
}
}
}
> * {
@apply fixed top-1/2 left-1/2 z-50 flex max-h-[calc(100%_-_2rem)] w-full -translate-x-1/2 -translate-y-1/2 scale-95 flex-col transition-all outline-hidden;
}
}
.dialog {
> * > header {
@apply flex flex-col;
}
> * > section {
@apply flex-1;
}
> * > footer {
@apply flex flex-col-reverse sm:flex-row sm:justify-end;
}
> * > :is(button, form[method='dialog']) {
@apply absolute;
}
> * > :is(button, form[method='dialog'] > button) {
@apply inline-flex items-center justify-center opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0;
}
> * > :is(button, form[method='dialog'] > button) > svg:not([class*='size-']) {
@apply size-4;
}
}
.alert-dialog {
> * > header {
@apply grid grid-rows-[auto_1fr] place-items-center text-center sm:place-items-start sm:text-start;
}
> * > header:has(> figure) {
@apply grid-rows-[auto_auto_1fr] sm:grid-rows-[auto_1fr];
}
> * > header > figure {
@apply m-0 shrink-0 sm:row-span-2 [&_svg]:pointer-events-none [&_svg]:shrink-0;
}
> * > header > figure:has(img) {
@apply overflow-hidden [&_img]:size-full [&_img]:object-cover;
}
> * > header:has(> figure) > :is(h2, p) {
@apply sm:col-start-2;
}
> * > footer {
@apply flex flex-col-reverse sm:flex-row sm:justify-end;
}
&:is([data-size='sm']) > * > header,
> [data-size='sm'] > header {
@apply sm:place-items-center sm:text-center;
}
&:is([data-size='sm']) > * > header:has(> figure),
> [data-size='sm'] > header:has(> figure) {
@apply sm:grid-rows-[auto_auto_1fr];
}
&:is([data-size='sm']) > * > header:has(> figure) > :is(h2, p),
> [data-size='sm'] > header:has(> figure) > :is(h2, p) {
@apply sm:col-start-auto;
}
&:is([data-size='sm']) > * > footer,
> [data-size='sm'] > footer {
@apply grid grid-cols-2 sm:grid sm:grid-cols-2;
}
}
}