Skip to content

Instantly share code, notes, and snippets.

@meduzen
Created February 26, 2025 20:35
Show Gist options
  • Save meduzen/327aa7bdf581ab92a1441a1cc35b3a4f to your computer and use it in GitHub Desktop.
Save meduzen/327aa7bdf581ab92a1441a1cc35b3a4f to your computer and use it in GitHub Desktop.
`<dialog>` component using Svelte 3 and plain CSS
:where(dialog) {
position: fixed;
margin: auto;
size: fit-content;
overscroll-behavior-y: contain;
}
:where(dialog:not([open])) {
display: none;
}
/* Hey reader! Do not forget to replace custom media queries if you don't use `postcss-preset-env`. */
@import url('../config/media-queries.css');
@import url('../media-queries/motion.css');
@import url('../media-queries/ui.css');
@keyframes dialog-background-light {
to { background: oklch(1 0 0 / .7); }
}
@keyframes dialog-background-dark {
to { background: oklch(0 0 0 / .6); }
}
.dialog {
/* helps the dialog to stay in the middle while closing and transitioning */
inset: 0;
inline-size: 100%;
max-inline-size: min(70rem, 100vw - 2rem);
padding: 0;
container: dialog / inline-size;
background: var(--bg);
border: 0;
border-radius: 1rem;
@media (--light) {
box-shadow: 0 1rem 7rem oklch(0 0 0 / .6);
}
@media (--dark) {
/* @todo: change this color */
border: .1rem solid #333;
}
@media (--motion) {
transition:
opacity .2s ease-in-out,
transform .2s ease-in-out,
visibility .2s ease-in-out;
&::backdrop {
background: transparent;
animation: dialog-background-light .5s ease-out forwards;
@media (--dark) {
animation-name: dialog-background-dark;
}
}
&:not([open]) {
display: block;
visibility: hidden;
opacity: 0;
transform: scale(.95) translateY(-2rem);
transform-origin: 50% 50%;
transition:
opacity .2s ease-in-out,
transform .2s ease-in-out,
visibility .2s ease-in-out .2s;
}
}
& > * {
padding-inline: 2rem;
}
}
.dialog__header {
padding-block: 1rem;
display: grid;
border-block-end: .1rem solid #eee;
/* border-block-end: .2rem solid var(--accent); */
@container (inline-size >= 20em) {
grid-template-columns: 1fr auto;
gap: 2rem;
}
}
.dialog__title {
margin: 0;
}
.dialog__close {
position: relative;
top: .3rem;
/* limit element height to its child button height */
align-self: baseline;
font-size: 0;
/* Put close button before title. */
@container (inline-size < 20em) {
order: -1;
left: -.7rem;
margin-block-start: 1em;
}
}
.dialog__closeBtn {
line-height: 0;
}
.dialog__closeBtnIcon {
@media (--phone) {
size: 3.2rem;
}
}
<!-- @todo: Take inspiration from https://github.com/meduzen/react-learning-udemy-schwarzmuller/blob/main/src/components/Dialog.jsx#L21-L32 if a callback system is needed later on. -->
<!-- @todo: Walk through the WCAG dialog pattern: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/. -->
<script>
/**
* Dialog title (currently a h2)
* @type {string}
*/
export let title
/**
* The text of the button closing the dialog.
* @type {string}
*/
export let closeLabel = 'Close'
export const show = () => dialog.showModal()
/**
* @type {HTMLDialogElement}
*/
let dialog
</script>
<!-- Unoptimized SVGs, should be redrawn. -->
<svg style="display: none;">
<!-- Heroicons (https://icones.js.org/collection/heroicons) -->
<symbol id="cross-path" viewBox="0 0 20 20">
<path fill="currentColor" d="M6.28 5.22a.75.75 0 0 0-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 1 0 1.06 1.06L10 11.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L11.06 10l3.72-3.72a.75.75 0 0 0-1.06-1.06L10 8.94L6.28 5.22Z"></path>
</symbol>
</svg>
<!-- HTML -->
<dialog class="dialog" bind:this={dialog} id={crypto.randomUUID()}>
<!-- header -->
<div class="dialog__header">
<h2 class="dialog__title">{title}</h2>
<form class="dialog__close" method="dialog">
<button
class="dialog__closeBtn"
value="0"
title={closeLabel}
aria-label={closeLabel}
>
<svg class="dialog__closeBtnIcon" width="20" height="20">
<use xlink:href="#cross-path"/>
</svg>
</button>
</form>
</div>
<!-- content -->
<div class="dialog__content">
<slot/>
</div>
</dialog>
<script>
import Dialog from './Dialog.svelte';
/**
* @type {Dialog}
*/
let dialog
const toggleButton = document.getElementById('toggle-settings')
toggleButton.addEventListener('click', () => dialog.show())
</script>
<Dialog
title="Settings"
closeLabel="Close settings"
bind:this={dialog}
>
<p>Some content.</p>
</Dialog>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment